diff --git a/.gitignore b/.gitignore index 67d693a9..1a4d6c6f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,85 @@ +#Manually added files + +src/NadekoBot/credentials.json +src/NadekoBot/data/NadekoBot.db +src/NadekoBot/data/musicdata + +# Created by https://www.gitignore.io/api/visualstudio,visualstudiocode,windows,linux,macos + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +### VisualStudio ### ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. @@ -6,26 +88,154 @@ *.user *.userosscache *.sln.docstates -*.pfx -obj/ + # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs -**/Bin/Release/ -**/Bin/PRIVATE/ -!**/Bin/Debug/opus.dll -!**/Bin/Debug/libsodium.dll -!**/Bin/Debug/Nito.AsyncEx.Enlightenment.dll -!**/Bin/Debug/Nito.AsyncEx.dll -!**/Bin/Debug/WebSocket4Net.dll -!**/Bin/Debug/sqlite3.dll -!**/Bin/Debug/credentials_example.json -NadekoBot/bin/debug/*.* -NadekoBot/bin/debug/data/permissions -NadekoBot/bin/debug/data/incidents -NadekoBot/bin/debug/data/musicdata -NadekoBot/bin/NadekoRelease/*.* -!NadekoBot/bin/Debug/data/currency_images/* -Tests/bin + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ # NuGet Packages *.nupkg @@ -35,11 +245,109 @@ Tests/bin !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config -NadekoBot/bin/Debug/data/nadekobot.sqlite -NadekoBot/bin/Debug/data/config.json -NadekoBot/bin/Debug/data/ServerSpecificConfigs.json -NadekoBot.sln.iml -.idea/workspace.xml -.idea/vcs.xml -.idea/modules.xml -NadekoBot/bin/Debug/data/config_xnaas.json \ No newline at end of file +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ + +### VisualStudio Patch ### +build/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 3f0608a0..0f1296be 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,4 @@ [submodule "discord.net"] path = discord.net - url = git://github.com/kwoth/discord.net.git -[submodule "ffmpeg-installer"] - path = ffmpeg-installer - url = https://github.com/kwoth/ffmpeg-installer + url = https://github.com/kwoth/discord.net + branch = dev diff --git a/ComprehensiveGuide.md b/ComprehensiveGuide.md deleted file mode 100644 index 1666d345..00000000 --- a/ComprehensiveGuide.md +++ /dev/null @@ -1,112 +0,0 @@ -________________________________________________________________________________ -*Thanks to @Flatbread and Mirai for making this guide* -________________________________________________________________________________ - -### Setting Up NadekoBot on Windows -#### Prerequisites -- 1) [NET Framework][NET Framework] 4.5.2 (or 4.6) -- 2) [FFMPEG][FFMPEG] -- 3) Google Account -- 4) Soundcloud Account (if you want soundcloud support) -- 5) [7zip][7zip] (or whatever you are using, WinRar) -- 6) [Notepad++][Notepad++] - -####Guide: - -- Create a folder, name it `Nadeko`. -- Head to [Releases][Releases]* and download `WINDOWS.-.nadeupdater.7z`. -- Copy `WINDOWS.-.nadeupdater.7z` to the `Nadeko` (folder we created before) and extract everything. -- You will see a file `NadekoUpdater.bat ` and a folder `publish ` after extraction. -- Run/Launch/Open the file `NadekoUpdater.bat ` and you will see it running in cmd.exe asking you with **3 options** *1-3*. - - 1) Stable release - current stable release, but might not contain all the newest Nadeko updates. - - 2) Newest release - release with all features/upgrades. - - 3) Exit -- Press `2` on your keyboard and hit `Enter`. Type `y` and hit `Enter` again. Downloading might take a while, so just be patient and wait. When download is done, press `3` on your keyboard and close the updater. -- You should have a new folder named `NadekoBot` inside the `Nadeko` folder we previously created. - -####Creating DiscordBot application -- Go to [DiscordApp][DiscordApp]. -- Log in with your Discord account. -- On the left side, press `New Application`. -- Fill out the `App Name` (your bot's name, in this case), put the image you want, and add an app description(optional). -- Create the application. -- Once the application is created, click on `Create a Bot User` and confirm it. -- Keep this window open for now. - -####Setting up Credentials.json file -- In our `NadekoBot` folder you should have `.json` file named `credentials_example.json`. (Note: If you do not see a **.json** after `credentials_example.json `, do not add the `**.json**`. You most likely have `"Hide file extensions"` enabled.) -- Rename `credentials_example.json` to `credentials.json`. -- Open the file with your [Notepad++][Notepad++]. -- In there you will see fields like `Token`, `ClientId`, `BotId`, `OwnerIDs`. -- In your [DiscordApp][DiscordApp], under `Bot User` part, you will see the `Token:click to reveal` part, click to reveal it. -- Copy your bot's token, and put it between `" "` in your `credentials.json` file. -- Copy `Client ID` and replace it with the example one in your `credentials.json`. -- Copy `Bot ID` and replace it with the example one in your `credentials.json`. -- Save your `credentials.json` but keep it open. We need to put your `User ID` and owner. - -####Inviting your bot to your server [Invite Guide][Invite Guide] -- Create a new server in Discord. -- Copy your `Client ID` from your [DiscordApp][DiscordApp]. -- Replace `12345678` in this link `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with your `Client ID`. -- Link should look like this: `https://discordapp.com/oauth2/authorize?client_id=**YOUR_CLENT_ID**&scope=bot&permissions=66186303`. -- Go to newly created link and pick the server we created, and click `Authorize`. -- Bot should be added to your server. - -####Starting the bot -- Enter your `NadekoBot` folder that should be (hopefully) in your `Nadeko` folder. -- Run `NadekoBot.exe` (Note: There is `NadekoBot.exe` and `NadekoBot.exe.config`, dont run the second one) -- Your bot should now be online in the server we added him to. -- Note: Your bot will be offline in case you close `NadekoBot.exe`. - -####Setting up OwnerIds: -- In the server where your bot is, in a text channel, type `.uid` -- Your `User ID` should show, copy it. -- Close `NadekoBot.exe` -- Replace your `User ID` in the `credentials.json` between `[ ]` and save the changes. -- Run `NadekoBot.exe` again. -- Now you are the bot owner. -- You can add `User IDs` from the other users by separating IDs with a comma if you want to have more owners. - -`*Alternatively, you can download nadekobot from [Releases][Releases] and extract the zip yourself. That is what updater does, except it makes it easier for you to update because it doesn't overwrite important files. If you are downloading releases you will have to be careful about your config, credentials, and other files you edited in order to preserve your data every time you update.` - -________________________________________________________________________________ - -#### Setting Up NadekoBot For Music -##### Prerequisites -- 1) [FFMPEG][FFMPEG] installed. -- 2) Setting up API keys. - -- Follow these steps on how to setup Google API keys: - - Go to [Google Console][Google Console] and log in. - - Create a new project (name does not matter). Once the project is created, go into "Enable and manage APIs." - - Under the "Other Popular APIs" section, enable `URL Shortener API` and `Custom Search Api`. Under the `YouTube APIs` section, enable `YouTube Data API`. - - On the left tab, access `Credentials`. Click `Create Credentials` button. Click on `API Key`, and then `Server Key` in the new window that appears. Enter in a name for the `Server Key`. A new window will appear with your `Google API key`. - - Copy the key. - - Open up `credentials.json`. - - For `"GoogleAPIKey"`, fill in with the new key we copied. -- Follow these steps on how to setup Soundcloud API key: - - Go to [Soundcloud][Soundcloud]. - - Enter a name for the app and create it. - - You will see a page with the title of your app, and a field labeled `Client ID`. Copy the ID. - - In `credentials.json`, fill in `"SoundcloudClientID"` with the copied ID. -- Restart your computer. - -##### Prerequisites for manual `ffmpeg` setup: -**Do this step in case you were not able to install `ffmpeg` with the installer.** -- Create a folder named `ffmpeg` in your main Windows directory. We will use **C:\ffmpeg** (for our guide) -- Download FFMPEG through the link https://ffmpeg.zeranoe.com/builds/ (download static build) -- Extract it using `7zip` and place the folder `ffmpeg-xxxxx-git-xxxxx-xxxx-static` inside **C:\ffmpeg** -- Before proceeding, check out this gif to set up `ffmpeg` PATH correctly http://i.imgur.com/aR5l1Hn.gif *(thanks to PooPeePants#7135)* -- Go to My Computer, right click and select Properties. On the left tab, select Advanced System Settings. Under the Advanced tab, select Environmental Variables near the bottom. One of the variables should be called "Path". Add a semi-colon (;) to the end followed by your FFMPEG's **bin** install location (**for example C:\ffmpeg\ffmpeg-xxxxx-git-xxxxx-xxxx-static\bin**). Save and close. -- Setup your API keys as explained above. -- Restart your computer. - -[NET Framework]: https://www.microsoft.com/en-us/download/details.aspx?id=48130 -[FFMPEG]: https://github.com/Soundofdarkness/FFMPEG-Installer -[7zip]: http://www.7-zip.org/download.html -[Releases]: //github.com/Kwoth/NadekoUpdater/releases/tag/v1.0 -[DiscordApp]: https://discordapp.com/developers/applications/me -[Notepad++]: https://notepad-plus-plus.org/ -[Invite Guide]: http://discord.kongslien.net/guide.html -[Google Console]: https://console.developers.google.com -[Soundcloud]: https://soundcloud.com/you/apps/new diff --git a/DockerGuide.md b/DockerGuide.md deleted file mode 100644 index 5a87bc22..00000000 --- a/DockerGuide.md +++ /dev/null @@ -1,54 +0,0 @@ -## Docker guide with digitalocean - -#####Prerequisites: -- Digital ocean account (you can use my [reflink][reflink] to support the project and get 10$ after you register) -- [PuTTY][PuTTY] -- A bot account - follow this [guide][guide] -- $5 -- Common sense - -#####Guide -- Click on the create droplet button -![img](http://i.imgur.com/g2ayOcC.png) - -- Pick one click apps and select docker on 14.04 - -![img](http://imgur.com/065Xkme.png) - -- Pick any droplet size you want (5$ will work ok-ish on a few servers) -- Pick location closest to your discord server's location -- Pick a hostname -![img](http://imgur.com/ifPKB6p.png) - -- Click create - -You will get an email from DigitalOcean with your credentials now. - -Open putty and type ip adress **you got in your email** with port 22 - -![img](http://imgur.com/Mh5ehsh.png) - -- Console will open and you will be prompted for a username, type `root`. -- Type in the password you got in the email. -- Confirm the password you just typed in. -- Type in the new password. -- Confirm new password. - -- When you are successfully logged in, type -`docker run --name nadeko -v /nadeko:/config uirel/nadeko` - -- Wait for it to download and at one point it is going to start throwing errors due to `credentials.json` being empty -- CTRL+C to exit that -- Type `docker stop nadeko` -- Type `nano /nadeko/credentials.json` and type in your `credentials` -- CTRL+X then CTRL+Y to save -- Type `docker start nadeko` -- Type `docker logs -f nadeko` to see the console output - -**Your bot is running, enjoy! o/** - -*When you want to update the bot, just type `docker restart nadeko` as it always downloads latest prerelease* - -[reflink]: http://m.do.co/c/46b4d3d44795/ -[PuTTY]: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html -[guide]: http://discord.kongslien.net/guide.html diff --git a/LinuxSetup.md b/LinuxSetup.md deleted file mode 100644 index 39acd7be..00000000 --- a/LinuxSetup.md +++ /dev/null @@ -1,270 +0,0 @@ -#Setting up NadekoBot on Linux - -####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 [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. - -#### Prerequisites -- Download [PuTTY][PuTTY] -- Download [CyberDuck][CyberDuck] - -#### Follow these steps - -- **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. -- 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. - -*(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**. After you done that, you are ready to write commands. - -**Copy and just paste** using **mouse right-click** (it should paste automatically) - -######MONO (Source: [Mono Source][Mono Source]) - -**1) Installing Mono** - -`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` - -`sudo apt-get update` - -Note if the command is not being initiated, hit **Enter** - -**2)** - -`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)** -*ONLY DEBIAN 8 and later* - -`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)** -*ONLY CentOS 7, Fedora 19 (and later)* - -`yum install yum-utils` - -`rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF"` - -`yum-config-manager --add-repo http://download.mono-project.com/repo/centos/` - -**3)** -*Mono Devel* - -`apt-get install mono-devel` - -**Type** `y` **hit Enter** - - -**4)** -Opus Voice Codec - -`sudo apt-get install libopus0 opus-tools` - -**Type** `y` **hit Enter** - -**5)** -`sudo apt-get install libopus-dev` - -**In case you are having issues with Mono where you get a random string and the bot won't run, do this:** - -`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` - -`apt-get install ca-certificates-mono` - -`mozroots --import --sync` - - -####FFMPEG - -**6)** -`apt-get install ffmpeg` - -**Type** `y` **hit Enter** - -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:** - -`sudo add-apt-repository ppa:mc3man/trusty-media` - -`sudo apt-get update` - -`sudo apt-get dist-upgrade` - -*Before executing* `sudo apt-get install ffmpeg` - -**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) - -In case you are not able to install it with installer ^up there, follow these steps: - -`sudo apt-get update` - -`echo "deb http://ftp.debian.org/debian jessie-backports main" | tee /etc/apt/sources.list.d/debian-backports.list` - -`sudo apt-get update` - -`sudo apt-get install ffmpeg -y` - -####Uncomplicated Firewall UFW - -**7)** -`apt-get install ufw` - -**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)** -`ufw status` - -**9)** -`ufw enable` - -**Type** `y` **hit Enter** - -**10)** -`sudo ufw allow ssh` - - - -**11)** -Unzip - -`apt-get install unzip` - -**12)** -TMUX - -`apt-get install tmux` - -**Type** `y` **hit Enter** - -####NOW WE NEED TO IMPORT SOME DISCORD CERTS -**13)** -`certmgr -ssl https://discordapp.com` - -**14)** -`certmgr -ssl https://gateway.discord.gg` - -Type `yes` and hit Enter **(three times - as it will ask for three times)** - - -**15)** -Create a new folder “nadeko” or anything you prefer - -`mkdir nadeko` - -**16)** -Move to “nadeko” folder (note `cd --` to go back the directory) - -`cd nadeko` - -**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, -it should look like `https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip` - -**17)** -Get the correct link, type `wget`, then *paste the link*, then hit **Enter**. - -`wget https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip` - -**^Do not copy-paste it** - -**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` - -`unzip NadekoBot.vx.x.zip` - -**^Do not copy-paste it** - -#####NOW TO SETUP NADEKO - -- 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. - -#####MAKE SURE YOU READ THE README BEFORE PROCEEDING - -- Copy the `credentials_example.json` to desktop -- EDIT it as it is guided here: [Readme][Readme] -- 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 - -Go back to **PuTTY**, `(hope its still running xD)` - -**19)** -Type/ Copy and hit **Enter**. - -`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.` - -`cd nadeko` - -**20)** -`mono NadekoBot.exe` - -**CHECK THE BOT IN DISCORD, IF EVERYTHING IS WORKING** - -Now time to **move bot to background** and to do that, press **CTRL+B**,release and press **D** (this will detach the nadeko session using TMUX), and you can finally close PuTTY now. - -Copy your CLIENT ID (that's in the same Developer page where you brought your token) and replace `12345678` in this link: `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with it. Go to that link and you will be able to add your bot to your server. - -**NOW YOU HAVE YOUR OWN NADEKO BOT** `Thanks to Kwoth <3` - -######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 **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)** --If you want to **kill** NadekoBot **session**, type `tmux kill-session -t nadeko` - -######TO RESTART YOUR BOT ALONG WITH THE WHOLE SERVER (for science): -**22)** -Open **PuTTY** and login as you have before, type `reboot` and hit Enter. - -######IF YOU WANT TO UPDATE YOUR BOT - -**FOLLOW THESE STEPS SERIALLY** - -- **-21 OR 22** -- **-19** -- **-16** -- **-17** -- **-18** -- **-20** - -HIT **CTRL+B**,release, press **D** and close **PuTTY** - -`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/ diff --git a/NadekoBot.sln b/NadekoBot.sln index d41210c6..7b8981b8 100644 --- a/NadekoBot.sln +++ b/NadekoBot.sln @@ -3,116 +3,49 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot", "NadekoBot\NadekoBot.csproj", "{27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Audio", "discord.net\src\Discord.Net.Audio.Net45\Discord.Net.Audio.csproj", "{7BFEF748-B934-4621-9B11-6302E3A9F6B3}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0B2F1537-4BF0-422B-A0DD-8F9CCEFB340F}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net", "discord.net\src\Discord.Net.Net45\Discord.Net.csproj", "{8D71A857-879A-4A10-859E-5FF824ED6688}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "NadekoBot", "src\NadekoBot\NadekoBot.xproj", "{45EC1473-C678-4857-A544-07DFE0D0B478}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Modules", "discord.net\src\Discord.Net.Modules.Net45\Discord.Net.Modules.csproj", "{3091164F-66AE-4543-A63D-167C1116241D}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "discord.net\src\Discord.Net\Discord.Net.xproj", "{91E9E7BD-75C9-4E98-84AA-2C271922E5C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Commands", "discord.net\src\Discord.Net.Commands.Net45\Discord.Net.Commands.csproj", "{1B5603B4-6F8F-4289-B945-7BAAE523D740}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "discord.net\src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}" EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{1b5603b4-6f8f-4289-b945-7baae523d740}*SharedItemsImports = 4 - discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{3091164f-66ae-4543-a63d-167c1116241d}*SharedItemsImports = 4 - discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{7bfef748-b934-4621-9b11-6302e3a9f6b3}*SharedItemsImports = 4 - discord.net\src\Discord.Net.Shared\Discord.Net.Shared.projitems*{8d71a857-879a-4a10-859e-5ff824ed6688}*SharedItemsImports = 4 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - FullDebug|Any CPU = FullDebug|Any CPU - FullDebug|x64 = FullDebug|x64 - NadekoRelease|Any CPU = NadekoRelease|Any CPU - NadekoRelease|x64 = NadekoRelease|x64 + GlobalNadeko|Any CPU = GlobalNadeko|Any CPU Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|x64.ActiveCfg = Debug|x64 - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|x64.Build.0 = Debug|x64 - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|x64.ActiveCfg = Debug|x64 - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|x64.Build.0 = Debug|x64 - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.NadekoRelease|Any CPU.ActiveCfg = NadekoRelease|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.NadekoRelease|Any CPU.Build.0 = NadekoRelease|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.NadekoRelease|x64.ActiveCfg = NadekoRelease|x64 - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.NadekoRelease|x64.Build.0 = NadekoRelease|x64 - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Release|Any CPU.ActiveCfg = Release|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Release|Any CPU.Build.0 = Release|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Release|x64.ActiveCfg = Release|x64 - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Release|x64.Build.0 = Release|x64 - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|x64.ActiveCfg = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Debug|x64.Build.0 = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|x64.ActiveCfg = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.FullDebug|x64.Build.0 = Debug|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|x64.ActiveCfg = Release|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.NadekoRelease|x64.Build.0 = Release|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.Build.0 = Release|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|x64.ActiveCfg = Release|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|x64.Build.0 = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|x64.ActiveCfg = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Debug|x64.Build.0 = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|x64.ActiveCfg = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.FullDebug|x64.Build.0 = Debug|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|x64.ActiveCfg = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.NadekoRelease|x64.Build.0 = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.Build.0 = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|x64.ActiveCfg = Release|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|x64.Build.0 = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.Debug|x64.ActiveCfg = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.Debug|x64.Build.0 = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|x64.ActiveCfg = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.FullDebug|x64.Build.0 = Debug|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|x64.ActiveCfg = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.NadekoRelease|x64.Build.0 = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.Release|x64.ActiveCfg = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.Release|x64.Build.0 = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|x64.ActiveCfg = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Debug|x64.Build.0 = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|x64.ActiveCfg = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.FullDebug|x64.Build.0 = Debug|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.ActiveCfg = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|Any CPU.Build.0 = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|x64.ActiveCfg = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.NadekoRelease|x64.Build.0 = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|x64.ActiveCfg = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|x64.Build.0 = Release|Any CPU + {45EC1473-C678-4857-A544-07DFE0D0B478}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45EC1473-C678-4857-A544-07DFE0D0B478}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45EC1473-C678-4857-A544-07DFE0D0B478}.GlobalNadeko|Any CPU.ActiveCfg = GlobalNadeko|Any CPU + {45EC1473-C678-4857-A544-07DFE0D0B478}.GlobalNadeko|Any CPU.Build.0 = GlobalNadeko|Any CPU + {45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45EC1473-C678-4857-A544-07DFE0D0B478}.Release|Any CPU.Build.0 = Release|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.GlobalNadeko|Any CPU.ActiveCfg = GlobalNadeko|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.GlobalNadeko|Any CPU.Build.0 = GlobalNadeko|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91E9E7BD-75C9-4E98-84AA-2C271922E5C2}.Release|Any CPU.Build.0 = Release|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.GlobalNadeko|Any CPU.ActiveCfg = GlobalNadeko|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.GlobalNadeko|Any CPU.Build.0 = GlobalNadeko|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {078DD7E6-943D-4D09-AFC2-D2BA58B76C9C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {45EC1473-C678-4857-A544-07DFE0D0B478} = {04929013-5BAB-42B0-B9B2-8F2BB8F16AF2} + EndGlobalSection EndGlobal diff --git a/NadekoBot/App.config b/NadekoBot/App.config deleted file mode 100644 index 75d0347a..00000000 --- a/NadekoBot/App.config +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/NadekoBot/Classes/BombermanGame.cs b/NadekoBot/Classes/BombermanGame.cs deleted file mode 100644 index 5b801e30..00000000 --- a/NadekoBot/Classes/BombermanGame.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace NadekoBot.Classes -{ - class BombermanGame - { - public ulong ChannelId { get; internal set; } - public bool Ended { get; internal set; } - } -} diff --git a/NadekoBot/Classes/DBHandler.cs b/NadekoBot/Classes/DBHandler.cs deleted file mode 100644 index db8dee81..00000000 --- a/NadekoBot/Classes/DBHandler.cs +++ /dev/null @@ -1,164 +0,0 @@ -using NadekoBot.DataModels; -using SQLite; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; - -namespace NadekoBot.Classes -{ - internal class DbHandler - { - public static DbHandler Instance { get; } = new DbHandler(); - - private string FilePath { get; } = "data/nadekobot.sqlite"; - - public SQLiteConnection Connection { get; set; } - - static DbHandler() { } - public DbHandler() - { - Connection = new SQLiteConnection(FilePath); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.CreateTable(); - Connection.Execute(Queries.TransactionTriggerQuery); - try - { - Connection.Execute(Queries.DeletePlaylistTriggerQuery); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - } - - internal T FindOne(Expression> p) where T : IDataModel, new() - { - return Connection.Table().Where(p).FirstOrDefault(); - - } - - internal IList FindAll(Expression> p) where T : IDataModel, new() - { - - return Connection.Table().Where(p).ToList(); - - } - - internal void DeleteWhere(Expression> p) where T : IDataModel, new() - { - var id = Connection.Table().Where(p).FirstOrDefault()?.Id; - if (id.HasValue) - Connection.Delete(id); - } - - internal HashSet GetAllRows() where T : IDataModel, new() - { - return new HashSet(Connection.Table()); - } - - internal CurrencyState GetStateByUserId(long id) - { - return Connection.Table().Where(x => x.UserId == id).FirstOrDefault(); - } - - internal T Delete(int id) where T : IDataModel, new() - { - var found = Connection.Find(id); - if (found != null) - Connection.Delete(found.Id); - return found; - } - - /// - /// Updates an existing object or creates a new one - /// - internal void Save(T o) where T : IDataModel, new() - { - var found = Connection.Find(o.Id); - if (found == null) - Connection.Insert(o, typeof(T)); - else - Connection.Update(o, typeof(T)); - } - - /// - /// Updates an existing object or creates a new one - /// - internal void SaveAll(IEnumerable ocol) where T : IDataModel, new() - { - foreach (var o in ocol) - Connection.InsertOrReplace(o); - } - - internal T GetRandom(Expression> p) where T : IDataModel, new() - { - var r = new Random(); - return Connection.Table().Where(p).ToList().OrderBy(x => r.Next()).FirstOrDefault(); - } - /// - /// - /// - /// Page number (0+) - /// - internal List GetPlaylistData(int num) - { - return Connection.Query( -@"SELECT mp.Name as 'Name',mp.Id as 'Id', mp.CreatorName as 'Creator', Count(*) as 'SongCnt' FROM MusicPlaylist as mp -INNER JOIN PlaylistSongInfo as psi -ON mp.Id = psi.PlaylistId -Group BY mp.Name -Order By mp.DateAdded desc -Limit 20 OFFSET ?", num * 20); - - } - - internal IEnumerable GetTopRichest(int n = 10) - { - return Connection.Table().OrderByDescending(cs => cs.Value).Take(n).ToList(); - } - } -} - -public class PlaylistData -{ - public string Name { get; set; } - public int Id { get; set; } - public string Creator { get; set; } - public int SongCnt { get; set; } -} - -public static class Queries -{ - public const string TransactionTriggerQuery = @" -CREATE TRIGGER IF NOT EXISTS OnTransactionAdded -AFTER INSERT ON CurrencyTransaction -BEGIN -INSERT OR REPLACE INTO CurrencyState (Id, UserId, Value, DateAdded) - VALUES (COALESCE((SELECT Id from CurrencyState where UserId = NEW.UserId),(SELECT COALESCE(MAX(Id),0)+1 from CurrencyState)), - NEW.UserId, - COALESCE((SELECT Value+New.Value FROM CurrencyState Where UserId = NEW.UserId),NEW.Value), - NEW.DateAdded); -END -"; - public const string DeletePlaylistTriggerQuery = @" -CREATE TRIGGER IF NOT EXISTS music_playlist -AFTER DELETE ON MusicPlaylist -FOR EACH ROW -BEGIN - DELETE FROM PlaylistSongInfo WHERE PlaylistId = OLD.Id; -END"; -} diff --git a/NadekoBot/Classes/Extensions.cs b/NadekoBot/Classes/Extensions.cs deleted file mode 100644 index 098e4732..00000000 --- a/NadekoBot/Classes/Extensions.cs +++ /dev/null @@ -1,378 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Net; -using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Extensions -{ - public static class Extensions - { - private static Random rng = new Random(); - - public static string Scramble(this string word) - { - - var letters = word.ToArray(); - var count = 0; - for (var i = 0; i < letters.Length; i++) - { - if (letters[i] == ' ') - continue; - - count++; - if (count <= letters.Length / 5) - continue; - - if (count % 3 == 0) - continue; - - if (letters[i] != ' ') - letters[i] = '_'; - } - return "`" + string.Join(" ", letters) + "`"; - } - public static string TrimTo(this string str, int num, bool hideDots = false) - { - if (num < 0) - throw new ArgumentOutOfRangeException(nameof(num), "TrimTo argument cannot be less than 0"); - if (num == 0) - return string.Empty; - if (num <= 3) - return string.Concat(str.Select(c => '.')); - if (str.Length < num) - return str; - return string.Concat(str.Take(num - 3)) + (hideDots ? "" : "..."); - } - /// - /// Removes trailing S or ES (if specified) on the given string if the num is 1 - /// - /// - /// - /// - /// String with the correct singular/plural form - public static string SnPl(this string str, int? num, bool es = false) - { - if (str == null) - throw new ArgumentNullException(nameof(str)); - if (num == null) - throw new ArgumentNullException(nameof(num)); - return num == 1 ? str.Remove(str.Length - 1, es ? 2 : 1) : str; - } - - /// - /// Sends a message to the channel from which this command is called. - /// - /// EventArg - /// Message to be sent - /// - public static async Task Send(this CommandEventArgs e, string message) - => await e.Channel.SendMessage(message).ConfigureAwait(false); - - /// - /// Sends a message to the channel from which MessageEventArg came. - /// - /// EventArg - /// Message to be sent - /// - public static async Task Send(this MessageEventArgs e, string message) - { - if (string.IsNullOrWhiteSpace(message)) - return; - await e.Channel.SendMessage(message).ConfigureAwait(false); - } - - /// - /// Sends a message to this channel. - /// - /// - /// - /// - public static async Task Send(this Channel c, string message) - { - await c.SendMessage(message).ConfigureAwait(false); - } - - /// - /// Sends a private message to this user. - /// - /// - /// - /// - public static async Task Send(this User u, string message) - { - await u.SendMessage(message).ConfigureAwait(false); - } - - /// - /// Replies to a user who invoked this command, message start with that user's mention. - /// - /// - /// - /// - public static async Task Reply(this CommandEventArgs e, string message) - { - await e.Channel.SendMessage(e.User.Mention + " " + message).ConfigureAwait(false); - } - - /// - /// Replies to a user who invoked this command, message start with that user's mention. - /// - /// - /// - /// - public static async Task Reply(this MessageEventArgs e, string message) - { - await e.Channel.SendMessage(e.User.Mention + " " + message).ConfigureAwait(false); - } - - /// - /// Randomizes element order in a list - /// - /// - /// - public static IList Shuffle(this IList list) - { - - // Thanks to @Joe4Evr for finding a bug in the old version of the shuffle - var provider = new RNGCryptoServiceProvider(); - var n = list.Count; - while (n > 1) - { - var box = new byte[(n / Byte.MaxValue) + 1]; - int boxSum; - do - { - provider.GetBytes(box); - boxSum = box.Sum(b => b); - } - while (!(boxSum < n * ((Byte.MaxValue * box.Length) / n))); - var k = (boxSum % n); - n--; - var value = list[k]; - list[k] = list[n]; - list[n] = value; - } - return list; - } - - /// - /// Shortens a string URL - /// - /// - /// - /// - public static async Task ShortenUrl(this string str) - { - try - { - var result = await SearchHelper.ShortenUrl(str).ConfigureAwait(false); - return result; - } - catch (WebException ex) - { - throw new InvalidOperationException("You must enable URL shortner in google developers console.", ex); - } - } - - public static string GetOnPage(this IEnumerable source, int pageIndex, int itemsPerPage = 5) - { - var items = source.Skip(pageIndex * itemsPerPage).Take(itemsPerPage); - if (!items.Any()) - { - return $"No items on page {pageIndex + 1}."; - } - var sb = new StringBuilder($"---page {pageIndex + 1} --\n"); - var itemsDC = items as IEnumerable>>; - var itemsDS = items as IEnumerable>; - if (itemsDC != null) - { - foreach (var item in itemsDC) - { - sb.Append($"{ Format.Code(item.Key)}\n"); - int i = 1; - var last = item.Value.Last(); - foreach (var value in item.Value) - { - if (last != value) - sb.AppendLine(" `├" + i++ + "─`" + Format.Bold(value)); - else - sb.AppendLine(" `└" + i++ + "─`" + Format.Bold(value)); - } - - } - } - else if (itemsDS != null) - { - foreach (var item in itemsDS) - { - sb.Append($"{ Format.Code(item.Key)}\n"); - sb.AppendLine(" `└─`" + Format.Bold(item.Value)); - } - - } - else - { - foreach (var item in items) - { - sb.Append($"{ Format.Code(item.ToString())} \n"); - } - } - - return sb.ToString(); - } - /// - /// Gets the program runtime - /// - /// - /// - /// - public static string GetRuntime(this DiscordClient c) => ".Net Framework 4.5.2"; - - public static string Matrix(this string s) - => - string.Concat(s.Select(c => c.ToString() + " ̵̢̬̜͉̞̭̖̰͋̉̎ͬ̔̇̌̀".TrimTo(rng.Next(0, 12), true))); - //.Replace("`", ""); - - public static void ForEach(this IEnumerable source, Action action) - { - foreach (var element in source) - { - action(element); - } - } - - //http://www.dotnetperls.com/levenshtein - public static int LevenshteinDistance(this string s, string t) - { - var n = s.Length; - var m = t.Length; - var d = new int[n + 1, m + 1]; - - // Step 1 - if (n == 0) - { - return m; - } - - if (m == 0) - { - return n; - } - - // Step 2 - for (var i = 0; i <= n; d[i, 0] = i++) - { - } - - for (var j = 0; j <= m; d[0, j] = j++) - { - } - - // Step 3 - for (var i = 1; i <= n; i++) - { - //Step 4 - for (var j = 1; j <= m; j++) - { - // Step 5 - var cost = (t[j - 1] == s[i - 1]) ? 0 : 1; - - // Step 6 - d[i, j] = Math.Min( - Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), - d[i - 1, j - 1] + cost); - } - } - // Step 7 - return d[n, m]; - } - - public static int KiB(this int value) => value * 1024; - public static int KB(this int value) => value * 1000; - - public static int MiB(this int value) => value.KiB() * 1024; - public static int MB(this int value) => value.KB() * 1000; - - public static int GiB(this int value) => value.MiB() * 1024; - 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) - { - if (format == null) - format = System.Drawing.Imaging.ImageFormat.Jpeg; - var stream = new MemoryStream(); - img.Save(stream, format); - stream.Position = 0; - return stream; - } - - /// - /// Merges Images into 1 Image and returns a bitmap. - /// - /// The Images you want to merge. - /// Merged bitmap - public static Bitmap Merge(this IEnumerable images, int reverseScaleFactor = 1) - { - var imageArray = images as Image[] ?? images.ToArray(); - if (!imageArray.Any()) return null; - var width = imageArray.Sum(i => i.Width); - var height = imageArray.First().Height; - var bitmap = new Bitmap(width / reverseScaleFactor, height / reverseScaleFactor); - var r = new Random(); - var offsetx = 0; - foreach (var img in imageArray) - { - var bm = new Bitmap(img); - for (var w = 0; w < img.Width; w++) - { - for (var h = 0; h < bitmap.Height; h++) - { - bitmap.SetPixel(w / reverseScaleFactor + offsetx, h, bm.GetPixel(w, h * reverseScaleFactor)); - } - } - offsetx += img.Width / reverseScaleFactor; - } - return bitmap; - } - - /// - /// Merges Images into 1 Image and returns a bitmap asynchronously. - /// - /// The Images you want to merge. - /// - /// Merged bitmap - public static async Task MergeAsync(this IEnumerable images, int reverseScaleFactor = 1) => - await Task.Run(() => images.Merge(reverseScaleFactor)).ConfigureAwait(false); - - public static string Unmention(this string str) => str.Replace("@", "ම"); - - public static Stream ToStream(this string str) - { - var sw = new StreamWriter(new MemoryStream()); - sw.Write(str); - sw.Flush(); - sw.BaseStream.Position = 0; - return sw.BaseStream; - } - - public static double UnixTimestamp(this DateTime dt) => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalSeconds; - - } -} diff --git a/NadekoBot/Classes/FlowersHandler.cs b/NadekoBot/Classes/FlowersHandler.cs deleted file mode 100644 index 57191c63..00000000 --- a/NadekoBot/Classes/FlowersHandler.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Threading.Tasks; - -namespace NadekoBot.Classes -{ - internal static class FlowersHandler - { - public static async Task AddFlowersAsync(Discord.User u, string reason, int amount, bool silent = false) - { - if (amount <= 0) - return; - await Task.Run(() => - { - DbHandler.Instance.Connection.Insert(new DataModels.CurrencyTransaction - { - Reason = reason, - UserId = (long)u.Id, - Value = amount, - }); - }).ConfigureAwait(false); - - if (silent) - return; - - var flows = amount + " " + NadekoBot.Config.CurrencySign; - - await u.SendMessage("👑Congratulations!👑\nYou received: " + flows).ConfigureAwait(false); - } - - public static async Task RemoveFlowers(Discord.User u, string reason, int amount, bool silent=false, string message="👎`Bot owner has taken {0}{1} from you.`") - { - if (amount <= 0) - return false; - var uid = (long)u.Id; - var state = DbHandler.Instance.FindOne(cs => cs.UserId == uid); - - if (state.Value < amount) - return false; - - DbHandler.Instance.Connection.Insert(new DataModels.CurrencyTransaction - { - Reason = reason, - UserId = (long)u.Id, - Value = -amount, - }); - - if (silent) - return true; - - await u.SendMessage(string.Format(message,amount,NadekoBot.Config.CurrencySign)).ConfigureAwait(false); - return true; - } - } -} diff --git a/NadekoBot/Classes/IncidentsHandler.cs b/NadekoBot/Classes/IncidentsHandler.cs deleted file mode 100644 index 3042f824..00000000 --- a/NadekoBot/Classes/IncidentsHandler.cs +++ /dev/null @@ -1,25 +0,0 @@ -using NadekoBot.DataModels; -using System; - -namespace NadekoBot.Classes -{ - internal static class IncidentsHandler - { - public static void Add(ulong serverId, ulong channelId, string text) - { - var def = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"INCIDENT: {text}"); - Console.ForegroundColor = def; - var incident = new Incident - { - ChannelId = (long)channelId, - ServerId = (long)serverId, - Text = text, - Read = false - }; - - DbHandler.Instance.Connection.Insert(incident, typeof(Incident)); - } - } -} diff --git a/NadekoBot/Classes/NadekoStats.cs b/NadekoBot/Classes/NadekoStats.cs deleted file mode 100644 index 57d730d4..00000000 --- a/NadekoBot/Classes/NadekoStats.cs +++ /dev/null @@ -1,288 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules.Administration.Commands; -using NadekoBot.Modules.Music; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -#if NADEKO_RELEASE -using System.IO; -#endif -using System.Linq; -using System.Net.Http; -using System.Reflection; -using System.Threading.Tasks; -using System.Timers; - -namespace NadekoBot -{ - public class NadekoStats - { - public static NadekoStats Instance { get; } = new NadekoStats(); - - public string BotVersion => $"{Assembly.GetExecutingAssembly().GetName().Name} v{Assembly.GetExecutingAssembly().GetName().Version}"; - - private int commandsRan = 0; - private string statsCache = ""; - private readonly Stopwatch statsStopwatch = new Stopwatch(); - - public int ServerCount { get; private set; } = 0; - public int TextChannelsCount { get; private set; } = 0; - public int VoiceChannelsCount { get; private set; } = 0; - - private readonly Timer commandLogTimer = new Timer() { Interval = 10000 }; - private readonly Timer carbonStatusTimer = new Timer() { Interval = 3600000 }; - - private static ulong messageCounter = 0; - public static ulong MessageCounter => messageCounter; - - static NadekoStats() { } - - private NadekoStats() - { - var commandService = NadekoBot.Client.GetService(); - - statsStopwatch.Start(); - - commandService.CommandExecuted += StatsCollector_RanCommand; - commandService.CommandFinished += CommandService_CommandFinished; - commandService.CommandErrored += CommandService_CommandFinished; - - Task.Run(StartCollecting); - - commandLogTimer.Start(); - - ServerCount = NadekoBot.Client.Servers.Count(); - var channels = NadekoBot.Client.Servers.SelectMany(s => s.AllChannels); - var channelsArray = channels as Channel[] ?? channels.ToArray(); - TextChannelsCount = channelsArray.Count(c => c.Type == ChannelType.Text); - VoiceChannelsCount = channelsArray.Count() - TextChannelsCount; - - NadekoBot.Client.MessageReceived += (s, e) => messageCounter++; - - NadekoBot.Client.JoinedServer += (s, e) => - { - try - { - ServerCount++; - TextChannelsCount += e.Server.TextChannels.Count(); - VoiceChannelsCount += e.Server.VoiceChannels.Count(); - //await SendUpdateToCarbon().ConfigureAwait(false); - } - catch { } - }; - NadekoBot.Client.LeftServer += (s, e) => - { - try - { - ServerCount--; - TextChannelsCount -= e.Server.TextChannels.Count(); - VoiceChannelsCount -= e.Server.VoiceChannels.Count(); - //await SendUpdateToCarbon().ConfigureAwait(false); - } - catch { } - }; - NadekoBot.Client.ChannelCreated += (s, e) => - { - try - { - if (e.Channel.IsPrivate) - return; - if (e.Channel.Type == ChannelType.Text) - TextChannelsCount++; - else if (e.Channel.Type == ChannelType.Voice) - VoiceChannelsCount++; - } - catch { } - }; - NadekoBot.Client.ChannelDestroyed += (s, e) => - { - try - { - if (e.Channel.IsPrivate) - return; - if (e.Channel.Type == ChannelType.Text) - TextChannelsCount--; - else if (e.Channel.Type == ChannelType.Voice) - VoiceChannelsCount--; - } - catch { } - }; - carbonStatusTimer.Elapsed += async (s, e) => await SendUpdateToCarbon().ConfigureAwait(false); - carbonStatusTimer.Start(); - } - - HttpClient carbonClient = new HttpClient(); - private async Task SendUpdateToCarbon() - { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.CarbonKey)) - return; - try - { - using (var content = new FormUrlEncodedContent(new Dictionary { - { "servercount", NadekoBot.Client.Servers.Count().ToString() }, - { "key", NadekoBot.Creds.CarbonKey } - })) - { - content.Headers.Clear(); - content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); - - var res = await carbonClient.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false); - }; - } - catch (Exception ex) - { - Console.WriteLine("Failed sending status update to carbon."); - Console.WriteLine(ex); - } - } - - public TimeSpan GetUptime() => - DateTime.Now - Process.GetCurrentProcess().StartTime; - - public string GetUptimeString() - { - var time = GetUptime(); - return time.Days + " days, " + time.Hours + " hours, and " + time.Minutes + " minutes."; - } - - public Task LoadStats() => - Task.Run(() => - { - var songs = MusicModule.MusicPlayers.Count(mp => mp.Value.CurrentSong != null); - var sb = new System.Text.StringBuilder(); - sb.AppendLine("`Author: Kwoth` `Library: Discord.Net`"); - sb.AppendLine($"`Bot Version: {BotVersion}`"); - sb.AppendLine($"`Bot id: {NadekoBot.Client.CurrentUser.Id}`"); - sb.Append("`Owners' Ids:` "); - sb.AppendLine("`" + String.Join(", ", NadekoBot.Creds.OwnerIds) + "`"); - sb.AppendLine($"`Uptime: {GetUptimeString()}`"); - sb.Append($"`Servers: {ServerCount}"); - sb.Append($" | TextChannels: {TextChannelsCount}"); - sb.AppendLine($" | VoiceChannels: {VoiceChannelsCount}`"); - sb.AppendLine($"`Commands Ran this session: {commandsRan}`"); - sb.AppendLine($"`Message queue size: {NadekoBot.Client.MessageQueue.Count}`"); - sb.Append($"`Greeted {ServerGreetCommand.Greeted} times.`"); - sb.AppendLine($" `| Playing {songs} songs, ".SnPl(songs) + - $"{MusicModule.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count)} queued.`"); - sb.AppendLine($"`Messages: {messageCounter} ({messageCounter / (double)GetUptime().TotalSeconds:F2}/sec)` `Heap: {Heap(false)}`"); - statsCache = sb.ToString(); - }); - - public string Heap(bool pass = true) => Math.Round((double)GC.GetTotalMemory(pass) / 1.MiB(), 2).ToString(); - - public async Task GetStats() - { - if (statsStopwatch.Elapsed.Seconds < 4 && - !string.IsNullOrWhiteSpace(statsCache)) return statsCache; - await LoadStats().ConfigureAwait(false); - statsStopwatch.Restart(); - return statsCache; - } - - private async Task StartCollecting() - { - var statsSw = new Stopwatch(); - while (true) - { - await Task.Delay(new TimeSpan(0, 30, 0)).ConfigureAwait(false); - statsSw.Start(); - try - { - var onlineUsers = await Task.Run(() => NadekoBot.Client.Servers.Sum(x => x.Users.Count())).ConfigureAwait(false); - var realOnlineUsers = await Task.Run(() => NadekoBot.Client.Servers - .Sum(x => x.Users.Count(u => u.Status == UserStatus.Online))) - .ConfigureAwait(false); - var connectedServers = NadekoBot.Client.Servers.Count(); - - Classes.DbHandler.Instance.Connection.Insert(new DataModels.Stats - { - OnlineUsers = onlineUsers, - RealOnlineUsers = realOnlineUsers, - Uptime = GetUptime(), - ConnectedServers = connectedServers, - 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 - { - Console.WriteLine("DB Exception in stats collecting."); - break; - } - } - } - - private static ConcurrentDictionary commandTracker = new ConcurrentDictionary(); - - private void CommandService_CommandFinished(object sender, CommandEventArgs e) - { - - DateTime dt; - if (!commandTracker.TryGetValue(e.Message.Id, out dt)) - return; -#if NADEKO_RELEASE - try - { - if (e is CommandErrorEventArgs) - { - var er = e as CommandErrorEventArgs; - if (er.ErrorType == CommandErrorType.Exception) - { - File.AppendAllText("errors.txt", $@"Command: {er.Command} -{er.Exception} -------------------------------------- -"); - Console.WriteLine($">>COMMAND ERRORED 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-----"); - } - - } - else - { - 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-----"); - } - } - catch { } -#endif - } - - private async void StatsCollector_RanCommand(object sender, CommandEventArgs e) - { - 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-----"); - commandsRan++; -#if !NADEKO_RELEASE - await Task.Run(() => - { - try - { - Classes.DbHandler.Instance.Connection.Insert(new DataModels.Command - { - ServerId = (long)(e.Server?.Id ?? 0), - ServerName = e.Server?.Name ?? "--Direct Message--", - ChannelId = (long)e.Channel.Id, - ChannelName = e.Channel.IsPrivate ? "--Direct Message" : e.Channel.Name, - UserId = (long)e.User.Id, - UserName = e.User.Name, - CommandName = e.Command.Text, - DateAdded = DateTime.Now - }); - } - catch (Exception ex) - { - Console.WriteLine("Probably unimportant error in ran command DB write."); - Console.WriteLine(ex); - } - }).ConfigureAwait(false); -#endif - } - } -} diff --git a/NadekoBot/Classes/ObservableConcurrentDictionary.cs b/NadekoBot/Classes/ObservableConcurrentDictionary.cs deleted file mode 100644 index cd364328..00000000 --- a/NadekoBot/Classes/ObservableConcurrentDictionary.cs +++ /dev/null @@ -1,204 +0,0 @@ -//-------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -// File: ObservableConcurrentDictionary.cs -// -//-------------------------------------------------------------------------- - -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Diagnostics; -using System.Threading; - -namespace System.Collections.Concurrent -{ - /// - /// Provides a thread-safe dictionary for use with data binding. - /// - /// Specifies the type of the keys in this collection. - /// Specifies the type of the values in this collection. - [DebuggerDisplay("Count={Count}")] - public class ObservableConcurrentDictionary : - ICollection>, IDictionary, - INotifyCollectionChanged, INotifyPropertyChanged - { - private readonly SynchronizationContext _context; - private readonly ConcurrentDictionary _dictionary; - - /// - /// Initializes an instance of the ObservableConcurrentDictionary class. - /// - public ObservableConcurrentDictionary() - { - _context = AsyncOperationManager.SynchronizationContext; - _dictionary = new ConcurrentDictionary(); - } - - /// Event raised when the collection changes. - public event NotifyCollectionChangedEventHandler CollectionChanged; - /// Event raised when a property on the collection changes. - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary. - /// - private void NotifyObserversOfChange() - { - var collectionHandler = CollectionChanged; - var propertyHandler = PropertyChanged; - if (collectionHandler != null || propertyHandler != null) - { - _context.Post(s => - { - if (collectionHandler != null) - { - collectionHandler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - if (propertyHandler != null) - { - propertyHandler(this, new PropertyChangedEventArgs("Count")); - propertyHandler(this, new PropertyChangedEventArgs("Keys")); - propertyHandler(this, new PropertyChangedEventArgs("Values")); - } - }, null); - } - } - - /// Attempts to add an item to the dictionary, notifying observers of any changes. - /// The item to be added. - /// Whether the add was successful. - private bool TryAddWithNotification(KeyValuePair item) - { - return TryAddWithNotification(item.Key, item.Value); - } - - /// Attempts to add an item to the dictionary, notifying observers of any changes. - /// The key of the item to be added. - /// The value of the item to be added. - /// Whether the add was successful. - private bool TryAddWithNotification(TKey key, TValue value) - { - bool result = _dictionary.TryAdd(key, value); - if (result) NotifyObserversOfChange(); - return result; - } - - /// Attempts to remove an item from the dictionary, notifying observers of any changes. - /// The key of the item to be removed. - /// The value of the item removed. - /// Whether the removal was successful. - private bool TryRemoveWithNotification(TKey key, out TValue value) - { - bool result = _dictionary.TryRemove(key, out value); - if (result) NotifyObserversOfChange(); - return result; - } - - /// Attempts to add or update an item in the dictionary, notifying observers of any changes. - /// The key of the item to be updated. - /// The new value to set for the item. - /// Whether the update was successful. - private void UpdateWithNotification(TKey key, TValue value) - { - _dictionary[key] = value; - NotifyObserversOfChange(); - } - - #region ICollection> Members - void ICollection>.Add(KeyValuePair item) - { - TryAddWithNotification(item); - } - - void ICollection>.Clear() - { - ((ICollection>)_dictionary).Clear(); - NotifyObserversOfChange(); - } - - bool ICollection>.Contains(KeyValuePair item) - { - return ((ICollection>)_dictionary).Contains(item); - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((ICollection>)_dictionary).CopyTo(array, arrayIndex); - } - - int ICollection>.Count { - get { return ((ICollection>)_dictionary).Count; } - } - - bool ICollection>.IsReadOnly { - get { return ((ICollection>)_dictionary).IsReadOnly; } - } - - bool ICollection>.Remove(KeyValuePair item) - { - TValue temp; - return TryRemoveWithNotification(item.Key, out temp); - } - #endregion - - #region IEnumerable> Members - IEnumerator> IEnumerable>.GetEnumerator() - { - return ((ICollection>)_dictionary).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((ICollection>)_dictionary).GetEnumerator(); - } - #endregion - - #region IDictionary Members - public void Add(TKey key, TValue value) - { - TryAddWithNotification(key, value); - } - - public bool ContainsKey(TKey key) - { - return _dictionary.ContainsKey(key); - } - - public ICollection Keys { - get { return _dictionary.Keys; } - } - - public bool Remove(TKey key) - { - TValue temp; - return TryRemoveWithNotification(key, out temp); - } - - public bool TryGetValue(TKey key, out TValue value) - { - return _dictionary.TryGetValue(key, out value); - } - - public bool TryAdd(TKey key, TValue value) - { - return TryAddWithNotification(key, value); - } - - public ICollection Values { - get { return _dictionary.Values; } - } - - public TValue this[TKey key] { - get { return _dictionary[key]; } - set { UpdateWithNotification(key, value); } - } - - public bool TryRemove(TKey key, out TValue value) - { - return TryRemoveWithNotification(key, out value); - } - #endregion - } -} \ No newline at end of file diff --git a/NadekoBot/Classes/SearchHelper.cs b/NadekoBot/Classes/SearchHelper.cs deleted file mode 100644 index e5bbcc2e..00000000 --- a/NadekoBot/Classes/SearchHelper.cs +++ /dev/null @@ -1,402 +0,0 @@ -using NadekoBot.Classes.JSONModels; -using NadekoBot.Extensions; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Security.Authentication; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Xml.Linq; - -namespace NadekoBot.Classes -{ - public enum RequestHttpMethod - { - Get, - Post - } - - public static class SearchHelper - { - private static DateTime lastRefreshed = DateTime.MinValue; - private static string token { get; set; } = ""; - - public static async Task GetResponseStreamAsync(string url, - IEnumerable> headers = null, RequestHttpMethod method = RequestHttpMethod.Get) - { - if (string.IsNullOrWhiteSpace(url)) - throw new ArgumentNullException(nameof(url)); - var cl = new HttpClient(); - cl.DefaultRequestHeaders.Clear(); - switch (method) - { - case RequestHttpMethod.Get: - if (headers != null) - { - foreach (var header in headers) - { - cl.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value); - } - } - return await cl.GetStreamAsync(url).ConfigureAwait(false); - case RequestHttpMethod.Post: - FormUrlEncodedContent formContent = null; - if (headers != null) - { - formContent = new FormUrlEncodedContent(headers); - } - var message = await cl.PostAsync(url, formContent).ConfigureAwait(false); - return await message.Content.ReadAsStreamAsync().ConfigureAwait(false); - default: - throw new NotImplementedException("That type of request is unsupported."); - } - } - - public static async Task GetResponseStringAsync(string url, - IEnumerable> headers = null, - RequestHttpMethod method = RequestHttpMethod.Get) - { - if (string.IsNullOrWhiteSpace(url)) - throw new ArgumentNullException(nameof(url)); - var cl = new HttpClient(); - cl.DefaultRequestHeaders.Clear(); - switch (method) - { - case RequestHttpMethod.Get: - if (headers != null) - { - foreach (var header in headers) - { - cl.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value); - } - } - return await cl.GetStringAsync(url).ConfigureAwait(false); - case RequestHttpMethod.Post: - FormUrlEncodedContent formContent = null; - if (headers != null) - { - formContent = new FormUrlEncodedContent(headers); - } - var message = await cl.PostAsync(url, formContent).ConfigureAwait(false); - return await message.Content.ReadAsStringAsync().ConfigureAwait(false); - default: - throw new NotImplementedException("That type of request is unsupported."); - } - } - - public static async Task GetAnimeData(string query) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - - await RefreshAnilistToken().ConfigureAwait(false); - - var link = "http://anilist.co/api/anime/search/" + Uri.EscapeUriString(query); - var smallContent = ""; - var cl = new RestSharp.RestClient("http://anilist.co/api"); - var rq = new RestSharp.RestRequest("/anime/search/" + Uri.EscapeUriString(query)); - rq.AddParameter("access_token", token); - smallContent = cl.Execute(rq).Content; - var smallObj = JArray.Parse(smallContent)[0]; - - rq = new RestSharp.RestRequest("/anime/" + smallObj["id"]); - rq.AddParameter("access_token", token); - var content = cl.Execute(rq).Content; - - return await Task.Run(() => JsonConvert.DeserializeObject(content)).ConfigureAwait(false); - } - - public static async Task GetMangaData(string query) - { - if (string.IsNullOrWhiteSpace(query)) - throw new ArgumentNullException(nameof(query)); - - await RefreshAnilistToken().ConfigureAwait(false); - - var link = "http://anilist.co/api/anime/search/" + Uri.EscapeUriString(query); - var smallContent = ""; - var cl = new RestSharp.RestClient("http://anilist.co/api"); - var rq = new RestSharp.RestRequest("/manga/search/" + Uri.EscapeUriString(query)); - rq.AddParameter("access_token", token); - smallContent = cl.Execute(rq).Content; - var smallObj = JArray.Parse(smallContent)[0]; - - rq = new RestSharp.RestRequest("/manga/" + smallObj["id"]); - rq.AddParameter("access_token", token); - var content = cl.Execute(rq).Content; - - return await Task.Run(() => JsonConvert.DeserializeObject(content)).ConfigureAwait(false); - } - - private static async Task RefreshAnilistToken() - { - if (DateTime.Now - lastRefreshed > TimeSpan.FromMinutes(29)) - lastRefreshed = DateTime.Now; - else - { - return; - } - var headers = new Dictionary { - {"grant_type", "client_credentials"}, - {"client_id", "kwoth-w0ki9"}, - {"client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z"}, - }; - var content = await GetResponseStringAsync( - "http://anilist.co/api/auth/access_token", - headers, - RequestHttpMethod.Post).ConfigureAwait(false); - - token = JObject.Parse(content)["access_token"].ToString(); - } - - public static async Task ValidateQuery(Discord.Channel ch, string query) - { - if (!string.IsNullOrEmpty(query.Trim())) return true; - await ch.Send("Please specify search parameters.").ConfigureAwait(false); - return false; - } - - public static async Task FindYoutubeUrlByKeywords(string keywords) - { - if (string.IsNullOrWhiteSpace(keywords)) - throw new ArgumentNullException(nameof(keywords), "Query not specified."); - if (keywords.Length > 150) - throw new ArgumentException("Query is too long."); - - //maybe it is already a youtube url, in which case we will just extract the id and prepend it with youtube.com?v= - var match = new Regex("(?:youtu\\.be\\/|v=)(?[\\da-zA-Z\\-_]*)").Match(keywords); - if (match.Length > 1) - { - return $"https://www.youtube.com/watch?v={match.Groups["id"].Value}"; - } - - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) - throw new InvalidCredentialException("Google API Key is missing."); - - var response = await GetResponseStringAsync( - $"https://www.googleapis.com/youtube/v3/search?" + - $"part=snippet&maxResults=1" + - $"&q={Uri.EscapeDataString(keywords)}" + - $"&key={NadekoBot.Creds.GoogleAPIKey}").ConfigureAwait(false); - JObject obj = JObject.Parse(response); - - var data = JsonConvert.DeserializeObject(response); - - if (data.items.Length > 0) - { - var toReturn = "http://www.youtube.com/watch?v=" + data.items[0].id.videoId.ToString(); - return toReturn; - } - else - return null; - } - - public static async Task> GetRelatedVideoIds(string id, int count = 1) - { - if (string.IsNullOrWhiteSpace(id)) - throw new ArgumentNullException(nameof(id)); - var match = new Regex("(?:youtu\\.be\\/|v=)(?[\\da-zA-Z\\-_]*)").Match(id); - if (match.Length > 1) - { - id = match.Groups["id"].Value; - } - var response = await GetResponseStringAsync( - $"https://www.googleapis.com/youtube/v3/search?" + - $"part=snippet&maxResults={count}&type=video" + - $"&relatedToVideoId={id}" + - $"&key={NadekoBot.Creds.GoogleAPIKey}").ConfigureAwait(false); - JObject obj = JObject.Parse(response); - - var data = JsonConvert.DeserializeObject(response); - - return data.items.Select(v => "http://www.youtube.com/watch?v=" + v.id.videoId); - } - - public static async Task GetPlaylistIdByKeyword(string query) - { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) - throw new ArgumentNullException(nameof(query)); - var match = new Regex("(?:youtu\\.be\\/|list=)(?[\\da-zA-Z\\-_]*)").Match(query); - if (match.Length > 1) - { - return match.Groups["id"].Value.ToString(); - } - var link = "https://www.googleapis.com/youtube/v3/search?part=snippet" + - "&maxResults=1&type=playlist" + - $"&q={Uri.EscapeDataString(query)}" + - $"&key={NadekoBot.Creds.GoogleAPIKey}"; - - var response = await GetResponseStringAsync(link).ConfigureAwait(false); - var data = JsonConvert.DeserializeObject(response); - JObject obj = JObject.Parse(response); - - return data.items.Length > 0 ? data.items[0].id.playlistId.ToString() : null; - } - - public static async Task> GetVideoIDs(string playlist, int number = 50) - { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) - { - throw new ArgumentNullException(nameof(playlist)); - } - if (number < 1) - throw new ArgumentOutOfRangeException(); - - string nextPageToken = null; - - List toReturn = new List(); - - do - { - var toGet = number > 50 ? 50 : number; - number -= toGet; - var link = - $"https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails" + - $"&maxResults={toGet}" + - $"&playlistId={playlist}" + - $"&key={NadekoBot.Creds.GoogleAPIKey}"; - if (!string.IsNullOrWhiteSpace(nextPageToken)) - link += $"&pageToken={nextPageToken}"; - var response = await GetResponseStringAsync(link).ConfigureAwait(false); - var data = await Task.Run(() => JsonConvert.DeserializeObject(response)).ConfigureAwait(false); - nextPageToken = data.nextPageToken; - toReturn.AddRange(data.items.Select(i => i.contentDetails.videoId)); - } while (number > 0 && !string.IsNullOrWhiteSpace(nextPageToken)); - - return toReturn; - } - - - public static async Task GetDanbooruImageLink(string tag) - { - var rng = new Random(); - - if (tag == "loli") //loli doesn't work for some reason atm - tag = "flat_chest"; - - var link = $"http://danbooru.donmai.us/posts?" + - $"page={rng.Next(0, 15)}"; - if (!string.IsNullOrWhiteSpace(tag)) - link += $"&tags={tag.Replace(" ", "_")}"; - - var webpage = await GetResponseStringAsync(link).ConfigureAwait(false); - var matches = Regex.Matches(webpage, "data-large-file-url=\"(?.*?)\""); - - if (matches.Count == 0) - return null; - return $"http://danbooru.donmai.us" + - $"{matches[rng.Next(0, matches.Count)].Groups["id"].Value}"; - } - - public static async Task GetGelbooruImageLink(string tag) - { - var headers = new Dictionary() { - {"User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1"}, - {"Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }, - }; - var url = - $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags={tag.Replace(" ", "_")}"; - var webpage = await GetResponseStringAsync(url, headers).ConfigureAwait(false); - var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); - if (matches.Count == 0) - return null; - var rng = new Random(); - var match = matches[rng.Next(0, matches.Count)]; - return matches[rng.Next(0, matches.Count)].Groups["url"].Value; - } - - public static async Task GetSafebooruImageLink(string tag) - { - var rng = new Random(); - var url = - $"http://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag.Replace(" ", "_")}"; - var webpage = await GetResponseStringAsync(url).ConfigureAwait(false); - var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); - if (matches.Count == 0) - return null; - var match = matches[rng.Next(0, matches.Count)]; - return matches[rng.Next(0, matches.Count)].Groups["url"].Value; - } - - public static async Task GetRule34ImageLink(string tag) - { - var rng = new Random(); - var url = - $"http://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag.Replace(" ", "_")}"; - var webpage = await GetResponseStringAsync(url).ConfigureAwait(false); - var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); - if (matches.Count == 0) - return null; - var match = matches[rng.Next(0, matches.Count)]; - return "http:" + matches[rng.Next(0, matches.Count)].Groups["url"].Value; - } - - - internal static async Task GetE621ImageLink(string tags) - { - try - { - var headers = new Dictionary() { - {"User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1"}, - {"Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }, - }; - var data = await GetResponseStreamAsync( - "http://e621.net/post/index.xml?tags=" + Uri.EscapeUriString(tags) + "%20order:random&limit=1", - headers); - var doc = XDocument.Load(data); - return doc.Descendants("file_url").FirstOrDefault().Value; - } - catch (Exception ex) - { - Console.WriteLine("Error in e621 search: \n" + ex); - return "Error, do you have too many tags?"; - } - } - - public static async Task ShortenUrl(string url) - { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) return url; - try - { - var httpWebRequest = - (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/urlshortener/v1/url?key=" + - NadekoBot.Creds.GoogleAPIKey); - httpWebRequest.ContentType = "application/json"; - httpWebRequest.Method = "POST"; - - using (var streamWriter = new StreamWriter(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false))) - { - var json = "{\"longUrl\":\"" + Uri.EscapeDataString(url) + "\"}"; - streamWriter.Write(json); - } - - var httpResponse = (await httpWebRequest.GetResponseAsync().ConfigureAwait(false)) as HttpWebResponse; - var responseStream = httpResponse.GetResponseStream(); - using (var streamReader = new StreamReader(responseStream)) - { - var responseText = await streamReader.ReadToEndAsync().ConfigureAwait(false); - return Regex.Match(responseText, @"""id"": ?""(?.+)""").Groups["id"].Value; - } - } - catch (Exception ex) - { - Console.WriteLine("Shortening of this url failed: " + url); - Console.WriteLine(ex.ToString()); - return url; - } - } - - public static string ShowInPrettyCode(IEnumerable items, Func howToPrint, int cols = 3) - { - var i = 0; - return "```xl\n" + string.Join("\n", items.GroupBy(item => (i++) / cols) - .Select(ig => string.Concat(ig.Select(el => howToPrint(el))))) - + $"\n```"; - } - } -} diff --git a/NadekoBot/Classes/ServerSpecificConfig.cs b/NadekoBot/Classes/ServerSpecificConfig.cs deleted file mode 100644 index 1642f75e..00000000 --- a/NadekoBot/Classes/ServerSpecificConfig.cs +++ /dev/null @@ -1,290 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.IO; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Classes -{ - internal class SpecificConfigurations - { - public static SpecificConfigurations Default { get; } = new SpecificConfigurations(); - public static bool Instantiated { get; private set; } - - private const string filePath = "data/ServerSpecificConfigs.json"; - - static SpecificConfigurations() { } - - private SpecificConfigurations() - { - - if (File.Exists(filePath)) - { - try - { - configs = JsonConvert - .DeserializeObject>( - File.ReadAllText(filePath), new JsonSerializerSettings() - { - Error = (s, e) => - { - if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels") - { - e.ErrorContext.Handled = true; - } - } - }); - } - catch (Exception ex) - { - Console.WriteLine($"Deserialization failing: {ex}"); - } - } - if (configs == null) - configs = new ConcurrentDictionary(); - Instantiated = true; - } - - private readonly ConcurrentDictionary configs; - - public IEnumerable AllConfigs => configs.Values; - - public ServerSpecificConfig Of(ulong id) => - configs.GetOrAdd(id, _ => new ServerSpecificConfig()); - - private readonly SemaphoreSlim saveLock = new SemaphoreSlim(1, 1); - - public async Task Save() - { - await saveLock.WaitAsync(); - try - { - await Task.Run(() => File.WriteAllText(filePath, JsonConvert.SerializeObject(configs, Formatting.Indented))); - } - finally - { - saveLock.Release(); - } - } - } - - internal class ServerSpecificConfig : INotifyPropertyChanged - { - [JsonProperty("VoicePlusTextEnabled")] - private bool voicePlusTextEnabled; - [JsonIgnore] - public bool VoicePlusTextEnabled { - get { return voicePlusTextEnabled; } - set { - voicePlusTextEnabled = value; - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - } - } - [JsonProperty("SendPrivateMessageOnMention")] - private bool sendPrivateMessageOnMention; - [JsonIgnore] - public bool SendPrivateMessageOnMention { - get { return sendPrivateMessageOnMention; } - set { - sendPrivateMessageOnMention = value; - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - } - } - - [JsonProperty("LogChannel")] - private ulong? logServerChannel = null; - [JsonIgnore] - public ulong? LogServerChannel { - get { return logServerChannel; } - set { - logServerChannel = value; - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - } - } - - [JsonIgnore] - private ObservableCollection logserverIgnoreChannels; - public ObservableCollection LogserverIgnoreChannels { - get { return logserverIgnoreChannels; } - set { - logserverIgnoreChannels = value; - if (value != null) - logserverIgnoreChannels.CollectionChanged += (s, e) => - { - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - }; - } - } - - [JsonProperty("LogPresenceChannel")] - private ulong? logPresenceChannel = null; - [JsonIgnore] - public ulong? LogPresenceChannel { - get { return logPresenceChannel; } - set { - logPresenceChannel = value; - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - } - } - - [JsonIgnore] - private ObservableConcurrentDictionary voiceChannelLog; - public ObservableConcurrentDictionary VoiceChannelLog { - get { return voiceChannelLog; } - set { - voiceChannelLog = value; - if (value != null) - voiceChannelLog.CollectionChanged += (s, e) => - { - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - }; - } - } - - [JsonIgnore] - private ObservableCollection listOfSelfAssignableRoles; - public ObservableCollection ListOfSelfAssignableRoles { - get { return listOfSelfAssignableRoles; } - set { - listOfSelfAssignableRoles = value; - if (value != null) - listOfSelfAssignableRoles.CollectionChanged += (s, e) => - { - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - }; - } - } - - - - [JsonIgnore] - private ulong autoAssignedRole = 0; - public ulong AutoAssignedRole { - get { return autoAssignedRole; } - set { - autoAssignedRole = value; - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - } - } - - [JsonIgnore] - private ObservableConcurrentDictionary generateCurrencyChannels; - public ObservableConcurrentDictionary GenerateCurrencyChannels { - get { return generateCurrencyChannels; } - set { - generateCurrencyChannels = value; - if (value != null) - generateCurrencyChannels.CollectionChanged += (s, e) => - { - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - }; - } - } - - [JsonIgnore] - private bool autoDeleteMessagesOnCommand = false; - public bool AutoDeleteMessagesOnCommand { - get { return autoDeleteMessagesOnCommand; } - set { - autoDeleteMessagesOnCommand = value; - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - } - } - - [JsonIgnore] - private bool exclusiveSelfAssignedRoles = false; - public bool ExclusiveSelfAssignedRoles { - get { return exclusiveSelfAssignedRoles; } - set { - exclusiveSelfAssignedRoles = value; - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - } - } - - - [JsonIgnore] - private ObservableCollection observingStreams; - public ObservableCollection ObservingStreams { - get { return observingStreams; } - set { - observingStreams = value; - if (value != null) - observingStreams.CollectionChanged += (s, e) => - { - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - }; - } - } - - [JsonIgnore] - private float defaultMusicVolume = 1f; - public float DefaultMusicVolume { - get { return defaultMusicVolume; } - set { - defaultMusicVolume = value; - if (!SpecificConfigurations.Instantiated) return; - OnPropertyChanged(); - } - } - - public ServerSpecificConfig() - { - ListOfSelfAssignableRoles = new ObservableCollection(); - ObservingStreams = new ObservableCollection(); - GenerateCurrencyChannels = new ObservableConcurrentDictionary(); - VoiceChannelLog = new ObservableConcurrentDictionary(); - LogserverIgnoreChannels = new ObservableCollection(); - } - - public event PropertyChangedEventHandler PropertyChanged = async delegate { await SpecificConfigurations.Default.Save().ConfigureAwait(false); }; - - private void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } - } - - public class StreamNotificationConfig : IEquatable - { - public string Username { get; set; } - public StreamType Type { get; set; } - public ulong ServerId { get; set; } - public ulong ChannelId { get; set; } - public bool LastStatus { get; set; } - - public enum StreamType - { - Twitch, - Beam, - Hitbox, - YoutubeGaming - } - - public bool Equals(StreamNotificationConfig other) => - this.Username.ToUpperInvariant().Trim() == other.Username.ToUpperInvariant().Trim() && - this.Type == other.Type && - this.ServerId == other.ServerId; - - public override int GetHashCode() - { - return (int)ServerId + Username.Length + (int)Type; - } - } -} diff --git a/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.dll b/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.dll deleted file mode 100644 index 8b121811..00000000 Binary files a/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.dll and /dev/null differ diff --git a/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.pdb b/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.pdb deleted file mode 100644 index f63024ad..00000000 Binary files a/NadekoBot/Classes/lib/ScaredFingers.UnitsConversion.pdb and /dev/null differ diff --git a/NadekoBot/Classes/lib/sqlite3.dll b/NadekoBot/Classes/lib/sqlite3.dll deleted file mode 100644 index c68c1f14..00000000 Binary files a/NadekoBot/Classes/lib/sqlite3.dll and /dev/null differ diff --git a/NadekoBot/Modules/Administration/AdministrationModule.cs b/NadekoBot/Modules/Administration/AdministrationModule.cs deleted file mode 100644 index 24effe3a..00000000 --- a/NadekoBot/Modules/Administration/AdministrationModule.cs +++ /dev/null @@ -1,901 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.DataModels; -using NadekoBot.Extensions; -using NadekoBot.Modules.Administration.Commands; -using NadekoBot.Modules.Permissions.Classes; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Administration -{ - internal class AdministrationModule : DiscordModule - { - public AdministrationModule() - { - commands.Add(new ServerGreetCommand(this)); - commands.Add(new LogCommand(this)); - commands.Add(new MessageRepeater(this)); - commands.Add(new PlayingRotate(this)); - commands.Add(new RatelimitCommand(this)); - commands.Add(new VoicePlusTextCommand(this)); - commands.Add(new CrossServerTextChannel(this)); - commands.Add(new SelfAssignedRolesCommand(this)); - commands.Add(new CustomReactionsCommands(this)); - commands.Add(new AutoAssignRole(this)); - commands.Add(new SelfCommands(this)); - commands.Add(new IncidentsCommands(this)); - - NadekoBot.Client.GetService().CommandExecuted += DeleteCommandMessage; - } - - private void DeleteCommandMessage(object sender, CommandEventArgs e) - { - if (e.Server == null || e.Channel.IsPrivate) - return; - var conf = SpecificConfigurations.Default.Of(e.Server.Id); - if (!conf.AutoDeleteMessagesOnCommand) - return; - try - { - e.Message.Delete(); - } - catch { } - } - - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Administration; - - public override void Install(ModuleManager manager) - { - - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - var client = manager.Client; - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand(Prefix + "delmsgoncmd") - .Description($"Toggles the automatic deletion of user's successful command message to prevent chat flood. **Server Manager Only.** | `{Prefix}delmsgoncmd`") - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - var conf = SpecificConfigurations.Default.Of(e.Server.Id); - conf.AutoDeleteMessagesOnCommand = !conf.AutoDeleteMessagesOnCommand; - await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); - if (conf.AutoDeleteMessagesOnCommand) - await e.Channel.SendMessage("❗`Now automatically deleting successful command invokations.`"); - else - await e.Channel.SendMessage("❗`Stopped automatic deletion of successful command invokations.`"); - - }); - - cgb.CreateCommand(Prefix + "restart") - .Description($"Restarts the bot. Might not work. **Bot Owner Only** | `{Prefix}restart`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - await e.Channel.SendMessage("`Restarting in 2 seconds...`"); - await Task.Delay(2000); - System.Diagnostics.Process.Start(System.Reflection.Assembly.GetExecutingAssembly().Location); - Environment.Exit(0); - }); - - cgb.CreateCommand(Prefix + "setrole").Alias(Prefix + "sr") - .Description($"Sets a role for a given user. **Needs Manage Roles Permissions.**| `{Prefix}sr @User Guest`") - .Parameter("user_name", ParameterType.Required) - .Parameter("role_name", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.CanManageRoles) - .Do(async e => - { - var userName = e.GetArg("user_name"); - var roleName = e.GetArg("role_name"); - - if (string.IsNullOrWhiteSpace(roleName)) return; - - if (!e.User.ServerPermissions.ManageRoles) - { - await e.Channel.SendMessage("You have insufficient permissions.").ConfigureAwait(false); - } - - var usr = e.Server.FindUsers(userName).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("You failed to supply a valid username").ConfigureAwait(false); - return; - } - - var role = e.Server.FindRoles(roleName).FirstOrDefault(); - if (role == null) - { - await e.Channel.SendMessage("You failed to supply a valid role").ConfigureAwait(false); - return; - } - - try - { - await usr.AddRoles(role).ConfigureAwait(false); - await e.Channel.SendMessage($"Successfully added role **{role.Name}** to user **{usr.Name}**").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Failed to add roles. Bot has insufficient permissions.\n").ConfigureAwait(false); - Console.WriteLine(ex.ToString()); - } - }); - - cgb.CreateCommand(Prefix + "removerole").Alias(Prefix + "rr") - .Description($"Removes a role from a given user. **Needs Manage Roles Permissions.**| `{Prefix}rr @User Admin`") - .Parameter("user_name", ParameterType.Required) - .Parameter("role_name", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.CanManageRoles) - .Do(async e => - { - var userName = e.GetArg("user_name"); - var roleName = e.GetArg("role_name"); - - if (string.IsNullOrWhiteSpace(roleName)) return; - - var usr = e.Server.FindUsers(userName).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("You failed to supply a valid username").ConfigureAwait(false); - return; - } - - var role = e.Server.FindRoles(roleName).FirstOrDefault(); - if (role == null) - { - await e.Channel.SendMessage("You failed to supply a valid role").ConfigureAwait(false); - return; - } - - try - { - await usr.RemoveRoles(role).ConfigureAwait(false); - await e.Channel.SendMessage($"Successfully removed role **{role.Name}** from user **{usr.Name}**").ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Failed to remove roles. Most likely reason: Insufficient permissions.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "renamerole") - .Alias(Prefix + "renr") - .Description($"Renames a role. Roles you are renaming must be lower than bot's highest role. **Manage Roles Permissions.** | `{Prefix}renr \"First role\" SecondRole`") - .Parameter("r1", ParameterType.Required) - .Parameter("r2", ParameterType.Required) - .AddCheck(new SimpleCheckers.ManageRoles()) - .Do(async e => - { - var r1 = e.GetArg("r1").Trim(); - var r2 = e.GetArg("r2").Trim(); - - var roleToEdit = e.Server.FindRoles(r1).FirstOrDefault(); - if (roleToEdit == null) - { - await e.Channel.SendMessage("Can't find that role.").ConfigureAwait(false); - return; - } - - try - { - if (roleToEdit.Position > e.Server.CurrentUser.Roles.Max(r => r.Position)) - { - await e.Channel.SendMessage("I can't edit roles higher than my highest role.").ConfigureAwait(false); - return; - } - await roleToEdit.Edit(r2); - await e.Channel.SendMessage("Role renamed.").ConfigureAwait(false); - } - catch (Exception) - { - await e.Channel.SendMessage("Failed to rename role. Probably insufficient permissions.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "removeallroles").Alias(Prefix + "rar") - .Description($"Removes all roles from a mentioned user. **Needs Manage Roles Permissions.**| `{Prefix}rar @User`") - .Parameter("user_name", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.CanManageRoles) - .Do(async e => - { - var userName = e.GetArg("user_name"); - - var usr = e.Server.FindUsers(userName).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("You failed to supply a valid username").ConfigureAwait(false); - return; - } - - try - { - await usr.RemoveRoles(usr.Roles.ToArray()).ConfigureAwait(false); - await e.Channel.SendMessage($"Successfully removed **all** roles from user **{usr.Name}**").ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Failed to remove roles. Most likely reason: Insufficient permissions.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "createrole").Alias(Prefix + "cr") - .Description($"Creates a role with a given name. **Needs Manage Roles Permissions.**| `{Prefix}cr Awesome Role`") - .Parameter("role_name", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.CanManageRoles) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("role_name"))) - return; - try - { - var r = await e.Server.CreateRole(e.GetArg("role_name")).ConfigureAwait(false); - await e.Channel.SendMessage($"Successfully created role **{r.Name}**.").ConfigureAwait(false); - } - catch (Exception) - { - await e.Channel.SendMessage(":warning: Unspecified error.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "rolecolor").Alias(Prefix + "rc") - .Parameter("role_name", ParameterType.Required) - .Parameter("r", ParameterType.Optional) - .Parameter("g", ParameterType.Optional) - .Parameter("b", ParameterType.Optional) - .Description($"Set a role's color to the hex or 0-255 rgb color value provided. **Needs Manage Roles Permissions.** | `{Prefix}rc Admin 255 200 100` or `{Prefix}rc Admin ffba55`") - .Do(async e => - { - if (!e.User.ServerPermissions.ManageRoles) - { - await e.Channel.SendMessage("You don't have permission to use this!").ConfigureAwait(false); - return; - } - - var args = e.Args.Where(s => s != string.Empty); - - if (args.Count() != 2 && args.Count() != 4) - { - await e.Channel.SendMessage("The parameters are invalid.").ConfigureAwait(false); - return; - } - - var role = e.Server.FindRoles(e.Args[0]).FirstOrDefault(); - - if (role == null) - { - await e.Channel.SendMessage("That role does not exist.").ConfigureAwait(false); - return; - } - try - { - var rgb = args.Count() == 4; - var arg1 = e.Args[1].Replace("#", ""); - - var red = Convert.ToByte(rgb ? int.Parse(arg1) : Convert.ToInt32(arg1.Substring(0, 2), 16)); - var green = Convert.ToByte(rgb ? int.Parse(e.Args[2]) : Convert.ToInt32(arg1.Substring(2, 2), 16)); - var blue = Convert.ToByte(rgb ? int.Parse(e.Args[3]) : Convert.ToInt32(arg1.Substring(4, 2), 16)); - - await role.Edit(color: new Color(red, green, blue)).ConfigureAwait(false); - await e.Channel.SendMessage($"Role {role.Name}'s color has been changed.").ConfigureAwait(false); - } - catch (Exception) - { - await e.Channel.SendMessage("Error occured, most likely invalid parameters or insufficient permissions.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "ban").Alias(Prefix + "b") - .Parameter("user", ParameterType.Required) - .Parameter("msg", ParameterType.Unparsed) - .Description($"Bans a user by id or name with an optional message. **Needs Ban Permissions.**| `{Prefix}b \"@some Guy\" Your behaviour is toxic.`") - .Do(async e => - { - var msg = e.GetArg("msg"); - var user = e.GetArg("user"); - if (e.User.ServerPermissions.BanMembers) - { - var usr = e.Server.FindUsers(user).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("User not found.").ConfigureAwait(false); - return; - } - if (!string.IsNullOrWhiteSpace(msg)) - { - await usr.SendMessage($"**You have been BANNED from `{e.Server.Name}` server.**\n" + - $"Reason: {msg}").ConfigureAwait(false); - await Task.Delay(2000).ConfigureAwait(false); // temp solution; give time for a message to be send, fu volt - } - try - { - await e.Server.Ban(usr, 7).ConfigureAwait(false); - - await e.Channel.SendMessage("Banned user " + usr.Name + " Id: " + usr.Id).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false); - } - } - }); - - cgb.CreateCommand(Prefix + "softban").Alias(Prefix + "sb") - .Parameter("user", ParameterType.Required) - .Parameter("msg", ParameterType.Unparsed) - .Description($"Bans and then unbans a user by id or name with an optional message. **Needs Ban Permissions.**| `{Prefix}sb \"@some Guy\" Your behaviour is toxic.`") - .Do(async e => - { - var msg = e.GetArg("msg"); - var user = e.GetArg("user"); - if (e.User.ServerPermissions.BanMembers) - { - var usr = e.Server.FindUsers(user).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("User not found.").ConfigureAwait(false); - return; - } - if (!string.IsNullOrWhiteSpace(msg)) - { - await usr.SendMessage($"**You have been SOFT-BANNED from `{e.Server.Name}` server.**\n" + - $"Reason: {msg}").ConfigureAwait(false); - await Task.Delay(2000).ConfigureAwait(false); // temp solution; give time for a message to be send, fu volt - } - try - { - await e.Server.Ban(usr, 7).ConfigureAwait(false); - await e.Server.Unban(usr).ConfigureAwait(false); - - await e.Channel.SendMessage("Soft-Banned user " + usr.Name + " Id: " + usr.Id).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false); - } - } - }); - - cgb.CreateCommand(Prefix + "kick").Alias(Prefix + "k") - .Parameter("user") - .Parameter("msg", ParameterType.Unparsed) - .Description($"Kicks a mentioned user. **Needs Kick Permissions.**| `{Prefix}k \"@some Guy\" Your behaviour is toxic.`") - .Do(async e => - { - var msg = e.GetArg("msg"); - var user = e.GetArg("user"); - if (e.User.ServerPermissions.KickMembers) - { - var usr = e.Server.FindUsers(user).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("User not found.").ConfigureAwait(false); - return; - } - if (!string.IsNullOrWhiteSpace(msg)) - { - await usr.SendMessage($"**You have been KICKED from `{e.Server.Name}` server.**\n" + - $"Reason: {msg}").ConfigureAwait(false); - await Task.Delay(2000).ConfigureAwait(false); // temp solution; give time for a message to be send, fu volt - } - try - { - await usr.Kick().ConfigureAwait(false); - await e.Channel.SendMessage("Kicked user " + usr.Name + " Id: " + usr.Id).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false); - } - } - }); - cgb.CreateCommand(Prefix + "mute") - .Description($"Mutes mentioned user or users. **Needs Mute Permissions.**| `{Prefix}mute \"@Someguy\"` or `{Prefix}mute \"@Someguy\" \"@Someguy\"`") - .Parameter("throwaway", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.MuteMembers) - { - await e.Channel.SendMessage("I most likely don't have the permission necessary for that.").ConfigureAwait(false); - return; - } - if (!e.Message.MentionedUsers.Any()) - return; - try - { - foreach (var u in e.Message.MentionedUsers) - { - await u.Edit(isMuted: true).ConfigureAwait(false); - } - await e.Channel.SendMessage("Mute successful").ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("I most likely don't have the permission necessary for that.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "unmute") - .Description($"Unmutes mentioned user or users. **Needs Mute Permissions.**| `{Prefix}unmute \"@Someguy\"` or `{Prefix}unmute \"@Someguy\" \"@Someguy\"`") - .Parameter("throwaway", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.MuteMembers) - { - await e.Channel.SendMessage("You do not have permission to do that.").ConfigureAwait(false); - return; - } - if (!e.Message.MentionedUsers.Any()) - return; - try - { - foreach (var u in e.Message.MentionedUsers) - { - await u.Edit(isMuted: false).ConfigureAwait(false); - } - await e.Channel.SendMessage("Unmute successful").ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("I most likely don't have the permission necessary for that.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "deafen") - .Alias(Prefix + "deaf") - .Description($"Deafens mentioned user or users. **Needs Deafen Permissions.**| `{Prefix}deaf \"@Someguy\"` or `{Prefix}deaf \"@Someguy\" \"@Someguy\"`") - .Parameter("throwaway", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.DeafenMembers) - { - await e.Channel.SendMessage("You do not have permission to do that.").ConfigureAwait(false); - return; - } - if (!e.Message.MentionedUsers.Any()) - return; - try - { - foreach (var u in e.Message.MentionedUsers) - { - await u.Edit(isDeafened: true).ConfigureAwait(false); - } - await e.Channel.SendMessage("Deafen successful").ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("I most likely don't have the permission necessary for that.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "undeafen") - .Alias(Prefix + "undef") - .Description($"Undeafens mentioned user or users. **Needs Deafen Permissions.** | `{Prefix}undef \"@Someguy\"` or `{Prefix}undef \"@Someguy\" \"@Someguy\"`") - .Parameter("throwaway", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.DeafenMembers) - { - await e.Channel.SendMessage("You do not have permission to do that.").ConfigureAwait(false); - return; - } - if (!e.Message.MentionedUsers.Any()) - return; - try - { - foreach (var u in e.Message.MentionedUsers) - { - await u.Edit(isDeafened: false).ConfigureAwait(false); - } - await e.Channel.SendMessage("Undeafen successful").ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("I most likely don't have the permission necessary for that.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "delvoichanl") - .Alias(Prefix + "dvch") - .Description($"Deletes a voice channel with a given name. **Needs Manage Channel Permissions.**| `{Prefix}dvch VoiceChannelName`") - .Parameter("channel_name", ParameterType.Required) - .Do(async e => - { - try - { - if (e.User.ServerPermissions.ManageChannels) - { - var ch = e.Server.FindChannels(e.GetArg("channel_name"), ChannelType.Voice).FirstOrDefault(); - if (ch == null) - return; - await ch.Delete().ConfigureAwait(false); - await e.Channel.SendMessage($"Removed channel **{e.GetArg("channel_name")}**.").ConfigureAwait(false); - } - } - catch - { - await e.Channel.SendMessage("Insufficient permissions."); - } - }); - - cgb.CreateCommand(Prefix + "creatvoichanl") - .Alias(Prefix + "cvch") - .Description($"Creates a new voice channel with a given name. **Needs Manage Channel Permissions.** | `{Prefix}cvch VoiceChannelName`") - .Parameter("channel_name", ParameterType.Required) - .Do(async e => - { - try - { - if (e.User.ServerPermissions.ManageChannels) - { - await e.Server.CreateChannel(e.GetArg("channel_name"), ChannelType.Voice).ConfigureAwait(false); - await e.Channel.SendMessage($"Created voice channel **{e.GetArg("channel_name")}**.").ConfigureAwait(false); - } - } - catch - { - await e.Channel.SendMessage("Insufficient permissions.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "deltxtchanl") - .Alias(Prefix + "dtch") - .Description($"Deletes a text channel with a given name. **Needs Manage Channel Permissions.** | `{Prefix}dtch TextChannelName`") - .Parameter("channel_name", ParameterType.Required) - .Do(async e => - { - try - { - if (e.User.ServerPermissions.ManageChannels) - { - var channel = e.Server.FindChannels(e.GetArg("channel_name"), ChannelType.Text).FirstOrDefault(); - if (channel == null) return; - await channel.Delete().ConfigureAwait(false); - await e.Channel.SendMessage($"Removed text channel **{e.GetArg("channel_name")}**.").ConfigureAwait(false); - } - } - catch - { - await e.Channel.SendMessage("Insufficient permissions.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "creatxtchanl") - .Alias(Prefix + "ctch") - .Description($"Creates a new text channel with a given name. **Needs Manage Channel Permissions.** | `{Prefix}ctch TextChannelName`") - .Parameter("channel_name", ParameterType.Required) - .Do(async e => - { - try - { - if (e.User.ServerPermissions.ManageChannels) - { - await e.Server.CreateChannel(e.GetArg("channel_name"), ChannelType.Text).ConfigureAwait(false); - await e.Channel.SendMessage($"Added text channel **{e.GetArg("channel_name")}**.").ConfigureAwait(false); - } - } - catch - { - await e.Channel.SendMessage("Insufficient permissions.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "settopic") - .Alias(Prefix + "st") - .Description($"Sets a topic on the current channel. **Needs Manage Channel Permissions.** | `{Prefix}st My new topic`") - .AddCheck(SimpleCheckers.ManageChannels()) - .Parameter("topic", ParameterType.Unparsed) - .Do(async e => - { - var topic = e.GetArg("topic")?.Trim() ?? ""; - await e.Channel.Edit(topic: topic).ConfigureAwait(false); - await e.Channel.SendMessage(":ok: **New channel topic set.**").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "setchanlname") - .Alias(Prefix + "schn") - .Description($"Changed the name of the current channel. **Needs Manage Channel Permissions.**| `{Prefix}schn NewName`") - .AddCheck(SimpleCheckers.ManageChannels()) - .Parameter("name", ParameterType.Unparsed) - .Do(async e => - { - var name = e.GetArg("name"); - if (string.IsNullOrWhiteSpace(name)) - return; - await e.Channel.Edit(name: name).ConfigureAwait(false); - await e.Channel.SendMessage(":ok: **New channel name set.**").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "heap") - .Description($"Shows allocated memory - **Bot Owner Only!** | `{Prefix}heap`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var heap = await Task.Run(() => NadekoStats.Instance.Heap()).ConfigureAwait(false); - await e.Channel.SendMessage($"`Heap Size:` {heap}").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "prune") - .Alias(Prefix + "clr") - .Description( - "`.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. **Needs Manage Messages Permissions**" + - $"| `{Prefix}prune` or `{Prefix}prune 5` or `{Prefix}prune @Someone` or `{Prefix}prune @Someone X`") - .Parameter("user_or_num", ParameterType.Optional) - .Parameter("num", ParameterType.Optional) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("user_or_num"))) // if nothing is set, clear nadeko's messages, no permissions required - { - var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User?.Id == e.Server.CurrentUser.Id)?.ToArray(); - if (msgs == null || !msgs.Any()) - return; - var toDelete = msgs as Message[] ?? msgs.ToArray(); - await e.Channel.DeleteMessages(toDelete).ConfigureAwait(false); - return; - } - if (!e.User.GetPermissions(e.Channel).ManageMessages) - return; - else if (!e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) - { - await e.Channel.SendMessage("💢I don't have the permission to manage messages."); - return; - } - int val; - if (int.TryParse(e.GetArg("user_or_num"), out val)) // if num is set in the first argument, - //delete that number of messages. - { - if (val <= 0) - return; - val++; - await e.Channel.DeleteMessages((await e.Channel.DownloadMessages(val).ConfigureAwait(false)).ToArray()).ConfigureAwait(false); - return; - } - //else if first argument is user - var usr = e.Server.FindUsers(e.GetArg("user_or_num")).FirstOrDefault(); - if (usr == null) - return; - val = 100; - if (!int.TryParse(e.GetArg("num"), out val)) - val = 100; - var mesgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User?.Id == usr.Id).Take(val); - if (mesgs == null || !mesgs.Any()) - return; - await e.Channel.DeleteMessages(mesgs as Message[] ?? mesgs.ToArray()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "die") - .Description($"Shuts the bot down and notifies users about the restart. **Bot Owner Only!** | `{Prefix}die`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - await e.Channel.SendMessage("`Shutting down.`").ConfigureAwait(false); - await Task.Delay(2000).ConfigureAwait(false); - Environment.Exit(0); - }); - - cgb.CreateCommand(Prefix + "setname") - .Alias(Prefix + "newnm") - .Description($"Give the bot a new name. **Bot Owner Only!** | `{Prefix}newnm BotName`") - .Parameter("new_name", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - if (e.GetArg("new_name") == null) return; - - await client.CurrentUser.Edit("", e.GetArg("new_name")).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "newavatar") - .Alias(Prefix + "setavatar") - .Description($"Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner Only!** | `{Prefix}setavatar http://i.imgur.com/xTG3a1I.jpg`") - .Parameter("img", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("img"))) - return; - // Gather user provided URL. - var avatarAddress = e.GetArg("img"); - var imageStream = await SearchHelper.GetResponseStreamAsync(avatarAddress).ConfigureAwait(false); - var image = System.Drawing.Image.FromStream(imageStream); - await client.CurrentUser.Edit("", avatar: image.ToStream()).ConfigureAwait(false); - - // Send confirm. - await e.Channel.SendMessage("New avatar set.").ConfigureAwait(false); - - // Save the image to disk. - image.Save("data/avatar.png", System.Drawing.Imaging.ImageFormat.Png); - }); - - cgb.CreateCommand(Prefix + "setgame") - .Description($"Sets the bots game. **Bot Owner Only!** | `{Prefix}setgame Playing with kwoth`") - .Parameter("set_game", ParameterType.Unparsed) - .Do(e => - { - if (!NadekoBot.IsOwner(e.User.Id) || e.GetArg("set_game") == null) return; - - client.SetGame(e.GetArg("set_game")); - }); - - cgb.CreateCommand(Prefix + "send") - .Description($"Send a message to someone on a different server through the bot. **Bot Owner Only!** | `{Prefix}send sid|u:uid Hello user!` or `{Prefix}send sid|c:cid Message to channel!` (cid = channel id, sid = server id)") - .Parameter("ids", ParameterType.Required) - .Parameter("msg", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var msg = e.GetArg("msg")?.Trim(); - - if (string.IsNullOrWhiteSpace(msg)) - return; - - var ids = e.GetArg("ids").Split('|'); - if (ids.Length != 2) - return; - var sid = ulong.Parse(ids[0]); - var server = NadekoBot.Client.Servers.Where(s => s.Id == sid).FirstOrDefault(); - - if (server == null) - return; - - if (ids[1].ToUpperInvariant().StartsWith("C:")) - { - var cid = ulong.Parse(ids[1].Substring(2)); - var channel = server.TextChannels.Where(c => c.Id == cid).FirstOrDefault(); - if (channel == null) - { - return; - } - await channel.SendMessage(msg); - } - else if (ids[1].ToUpperInvariant().StartsWith("U:")) - { - var uid = ulong.Parse(ids[1].Substring(2)); - var user = server.Users.Where(u => u.Id == uid).FirstOrDefault(); - if (user == null) - { - return; - } - await user.SendMessage(msg); - } - else - { - await e.Channel.SendMessage("`Invalid format.`"); - } - }); - - cgb.CreateCommand(Prefix + "mentionrole") - .Alias(Prefix + "menro") - .Description($"Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. | `{Prefix}menro RoleName`") - .Parameter("roles", ParameterType.Unparsed) - .Do(async e => - { - await Task.Run(async () => - { - if (!e.User.ServerPermissions.MentionEveryone) return; - var arg = e.GetArg("roles").Split(',').Select(r => r.Trim()); - string send = $"--{e.User.Mention} has invoked a mention on the following roles--"; - foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str))) - { - var role = e.Server.FindRoles(roleStr).FirstOrDefault(); - if (role == null) continue; - send += $"\n`{role.Name}`\n"; - send += string.Join(", ", role.Members.Select(r => r.Mention)); - } - - while (send.Length > 2000) - { - var curstr = send.Substring(0, 2000); - await - e.Channel.Send(curstr.Substring(0, - curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1)).ConfigureAwait(false); - send = curstr.Substring(curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1) + - send.Substring(2000); - } - await e.Channel.Send(send).ConfigureAwait(false); - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "unstuck") - .Description($"Clears the message queue. **Bot Owner Only!** | `{Prefix}unstuck`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(e => - { - NadekoBot.Client.MessageQueue.Clear(); - }); - - cgb.CreateCommand(Prefix + "donators") - .Description($"List of lovely people who donated to keep this project alive. | `{Prefix}donators`") - .Do(async e => - { - await Task.Run(async () => - { - var rows = DbHandler.Instance.GetAllRows(); - var donatorsOrdered = rows.OrderByDescending(d => d.Amount); - string str = $"**Thanks to the people listed below for making this project happen!**\n"; - - await e.Channel.SendMessage(str + string.Join("⭐", donatorsOrdered.Select(d => d.UserName))).ConfigureAwait(false); - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "donadd") - .Description($"Add a donator to the database. **Kwoth Only** | `{Prefix}donadd Donate Amount`") - .Parameter("donator") - .Parameter("amount") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - await Task.Run(() => - { - var donator = e.Server.FindUsers(e.GetArg("donator")).FirstOrDefault(); - var amount = int.Parse(e.GetArg("amount")); - if (donator == null) return; - try - { - DbHandler.Instance.Connection.Insert(new Donator - { - Amount = amount, - UserName = donator.Name, - UserId = (long)donator.Id - }); - e.Channel.SendMessage("Successfuly added a new donator. 👑").ConfigureAwait(false); - } - catch { } - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "announce") - .Description($"Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | `{Prefix}announce Useless spam`") - .Parameter("msg", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - foreach (var ch in NadekoBot.Client.Servers.Select(s => s.DefaultChannel)) - { - await ch.SendMessage(e.GetArg("msg")).ConfigureAwait(false); - } - - await e.Channel.SendMessage(":ok:").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "savechat") - .Description($"Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `{Prefix}savechat 150`") - .Parameter("cnt", ParameterType.Required) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var cntstr = e.GetArg("cnt")?.Trim(); - int cnt; - if (!int.TryParse(cntstr, out cnt)) - return; - ulong? lastmsgId = null; - var sb = new StringBuilder(); - var msgs = new List(cnt); - while (cnt > 0) - { - var dlcnt = cnt < 100 ? cnt : 100; - - var dledMsgs = await e.Channel.DownloadMessages(dlcnt, lastmsgId); - if (!dledMsgs.Any()) - break; - msgs.AddRange(dledMsgs); - lastmsgId = msgs[msgs.Count - 1].Id; - cnt -= 100; - } - await e.User.SendFile($"Chatlog-{e.Server.Name}/#{e.Channel.Name}-{DateTime.Now}.txt", JsonConvert.SerializeObject(new { Messages = msgs.Select(s => s.ToString()) }, Formatting.Indented).ToStream()).ConfigureAwait(false); - }); - - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/AutoAssignRole.cs b/NadekoBot/Modules/Administration/Commands/AutoAssignRole.cs deleted file mode 100644 index 373840cb..00000000 --- a/NadekoBot/Modules/Administration/Commands/AutoAssignRole.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Linq; - -namespace NadekoBot.Modules.Administration.Commands -{ - class AutoAssignRole : DiscordCommand - { - public AutoAssignRole(DiscordModule module) : base(module) - { - NadekoBot.OnReady += () => NadekoBot.Client.UserJoined += (s, e) => - { - try - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - - var role = e.Server.Roles.Where(r => r.Id == config.AutoAssignedRole).FirstOrDefault(); - - if (role == null) - return; - - e.User.AddRoles(role); - } - catch (Exception ex) - { - Console.WriteLine($"aar exception. {ex}"); - } - }; - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "autoassignrole") - .Alias(Module.Prefix + "aar") - .Description($"Automaticaly assigns a specified role to every user who joins the server. **Needs Manage Roles Permissions.** |`{Prefix}aar` to disable, `{Prefix}aar Role Name` to enable") - .Parameter("role", ParameterType.Unparsed) - .AddCheck(new SimpleCheckers.ManageRoles()) - .Do(async e => - { - if (!e.Server.CurrentUser.ServerPermissions.ManageRoles) - { - await e.Channel.SendMessage("I do not have the permission to manage roles.").ConfigureAwait(false); - return; - } - var r = e.GetArg("role")?.Trim(); - - var config = SpecificConfigurations.Default.Of(e.Server.Id); - - if (string.IsNullOrWhiteSpace(r)) //if role is not specified, disable - { - config.AutoAssignedRole = 0; - - await e.Channel.SendMessage("`Auto assign role on user join is now disabled.`").ConfigureAwait(false); - return; - } - var role = e.Server.FindRoles(r).FirstOrDefault(); - - if (role == null) - { - await e.Channel.SendMessage("💢 `Role not found.`").ConfigureAwait(false); - return; - } - - config.AutoAssignedRole = role.Id; - await e.Channel.SendMessage("`Auto assigned role is set.`").ConfigureAwait(false); - - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs b/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs deleted file mode 100644 index 88f6cd40..00000000 --- a/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace NadekoBot.Modules.Administration.Commands -{ - class CrossServerTextChannel : DiscordCommand - { - public CrossServerTextChannel(DiscordModule module) : base(module) - { - NadekoBot.OnReady += () => - { - NadekoBot.Client.MessageReceived += async (s, e) => - { - try - { - if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - foreach (var subscriber in Subscribers) - { - var set = subscriber.Value; - if (!set.Contains(e.Channel)) - continue; - foreach (var chan in set.Except(new[] { e.Channel })) - { - await chan.SendMessage(GetText(e.Server, e.Channel, e.User, e.Message)).ConfigureAwait(false); - } - } - } - catch { } - }; - NadekoBot.Client.MessageUpdated += async (s, e) => - { - try - { - if (e.After?.User?.Id == null || e.After.User.Id == NadekoBot.Client.CurrentUser.Id) return; - foreach (var subscriber in Subscribers) - { - var set = subscriber.Value; - if (!set.Contains(e.Channel)) - continue; - foreach (var chan in set.Except(new[] { e.Channel })) - { - var msg = chan.Messages - .FirstOrDefault(m => - m.RawText == GetText(e.Server, e.Channel, e.User, e.Before)); - if (msg != default(Message)) - await msg.Edit(GetText(e.Server, e.Channel, e.User, e.After)).ConfigureAwait(false); - } - } - - } - catch { } - }; - }; - } - - private string GetText(Server server, Channel channel, User user, Message message) => - $"**{server.Name} | {channel.Name}** `{user.Name}`: " + message.RawText; - - public static readonly ConcurrentDictionary> Subscribers = new ConcurrentDictionary>(); - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "scsc") - .Description("Starts an instance of cross server channel. You will get a token as a DM " + - $"that other people will use to tune in to the same instance. **Bot Owner Only.** | `{Prefix}scsc`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var token = new Random().Next(); - var set = new HashSet(); - if (Subscribers.TryAdd(token, set)) - { - set.Add(e.Channel); - await e.User.SendMessage("This is your CSC token:" + token.ToString()).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Module.Prefix + "jcsc") - .Description($"Joins current channel to an instance of cross server channel using the token. **Needs Manage Server Permissions.**| `{Prefix}jcsc`") - .Parameter("token") - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - int token; - if (!int.TryParse(e.GetArg("token"), out token)) - return; - HashSet set; - if (!Subscribers.TryGetValue(token, out set)) - return; - set.Add(e.Channel); - await e.Channel.SendMessage(":ok:").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "lcsc") - .Description($"Leaves Cross server channel instance from this channel. **Needs Manage Server Permissions.**| `{Prefix}lcsc`") - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - foreach (var subscriber in Subscribers) - { - subscriber.Value.Remove(e.Channel); - } - await e.Channel.SendMessage(":ok:").ConfigureAwait(false); - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs deleted file mode 100644 index 7ed71e8b..00000000 --- a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs +++ /dev/null @@ -1,224 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace NadekoBot.Modules.Administration.Commands -{ - class CustomReactionsCommands : DiscordCommand - { - public CustomReactionsCommands(DiscordModule module) : base(module) - { - - } - - internal override void Init(CommandGroupBuilder cgb) - { - var Prefix = Module.Prefix; - - cgb.CreateCommand(Prefix + "addcustreact") - .Alias(Prefix + "acr") - .Description($"Add a custom reaction. Guide here: **Bot Owner Only!** | `{Prefix}acr \"hello\" Hi there %user%`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Parameter("name", ParameterType.Required) - .Parameter("message", ParameterType.Unparsed) - .Do(async e => - { - var name = e.GetArg("name"); - var message = e.GetArg("message")?.Trim(); - if (string.IsNullOrWhiteSpace(message)) - { - await e.Channel.SendMessage($"Incorrect command usage. See -h {Prefix}acr for correct formatting").ConfigureAwait(false); - return; - } - if (NadekoBot.Config.CustomReactions.ContainsKey(name)) - NadekoBot.Config.CustomReactions[name].Add(message); - else - NadekoBot.Config.CustomReactions.Add(name, new System.Collections.Generic.List() { message }); - await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); - await e.Channel.SendMessage($"Added {name} : {message}").ConfigureAwait(false); - - }); - - cgb.CreateCommand(Prefix + "listcustreact") - .Alias(Prefix + "lcr") - .Description($"Lists custom reactions (paginated with 30 commands per page). Use 'all' instead of page number to get all custom reactions DM-ed to you. |`{Prefix}lcr 1`") - .Parameter("num", ParameterType.Required) - .Do(async e => - { - var numStr = e.GetArg("num"); - - if (numStr.ToUpperInvariant() == "ALL") - { - var fullstr = String.Join("\n", NadekoBot.Config.CustomReactions.Select(kvp => kvp.Key)); - do - { - var str = string.Concat(fullstr.Take(1900)); - fullstr = new string(fullstr.Skip(1900).ToArray()); - await e.User.SendMessage("```xl\n" + str + "```"); - } while (fullstr.Length != 0); - return; - } - int num; - if (!int.TryParse(numStr, out num) || num <= 0) num = 1; - var cmds = GetCustomsOnPage(num - 1); - if (!cmds.Any()) - { - await e.Channel.SendMessage("`There are no custom reactions.`"); - } - else - { - string result = SearchHelper.ShowInPrettyCode(cmds, s => $"{s,-25}"); //People prefer starting with 1 - await e.Channel.SendMessage($"`Showing page {num}:`\n" + result).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "showcustreact") - .Alias(Prefix + "scr") - .Description($"Shows all possible responses from a single custom reaction. |`{Prefix}scr %mention% bb`") - .Parameter("name", ParameterType.Unparsed) - .Do(async e => - { - var name = e.GetArg("name")?.Trim(); - if (string.IsNullOrWhiteSpace(name)) - return; - if (!NadekoBot.Config.CustomReactions.ContainsKey(name)) - { - await e.Channel.SendMessage("`Can't find that custom reaction.`").ConfigureAwait(false); - return; - } - var items = NadekoBot.Config.CustomReactions[name]; - var message = new StringBuilder($"Responses for {Format.Bold(name)}:\n"); - var last = items.Last(); - - int i = 1; - foreach (var reaction in items) - { - message.AppendLine($"[{i++}] " + Format.Code(Format.Escape(reaction))); - } - await e.Channel.SendMessage(message.ToString()); - }); - - cgb.CreateCommand(Prefix + "editcustreact") - .Alias(Prefix + "ecr") - .Description($"Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `{Prefix}ecr \"%mention% disguise\" 2 Test 123`") - .Parameter("name", ParameterType.Required) - .Parameter("index", ParameterType.Required) - .Parameter("message", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var name = e.GetArg("name")?.Trim(); - if (string.IsNullOrWhiteSpace(name)) - return; - var indexstr = e.GetArg("index")?.Trim(); - if (string.IsNullOrWhiteSpace(indexstr)) - return; - var msg = e.GetArg("message")?.Trim(); - if (string.IsNullOrWhiteSpace(msg)) - return; - - - - if (!NadekoBot.Config.CustomReactions.ContainsKey(name)) - { - await e.Channel.SendMessage("`Could not find given commandname`").ConfigureAwait(false); - return; - } - - int index; - if (!int.TryParse(indexstr, out index) || index < 1 || index > NadekoBot.Config.CustomReactions[name].Count) - { - await e.Channel.SendMessage("`Invalid index.`").ConfigureAwait(false); - return; - } - index = index - 1; - NadekoBot.Config.CustomReactions[name][index] = msg; - - await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); - await e.Channel.SendMessage($"Edited response #{index + 1} from `{name}`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "delcustreact") - .Alias(Prefix + "dcr") - .Description($"Deletes a custom reaction with given name (and optional index). **Bot Owner Only.**| `{Prefix}dcr \"Reaction Name\"` or `{Prefix}dcr \"Reaction Name\" 3`") - .Parameter("name", ParameterType.Required) - .Parameter("index", ParameterType.Optional) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var name = e.GetArg("name")?.Trim(); - if (string.IsNullOrWhiteSpace(name)) - return; - if (!NadekoBot.Config.CustomReactions.ContainsKey(name)) - { - await e.Channel.SendMessage("Could not find given commandname").ConfigureAwait(false); - return; - } - string message = ""; - int index; - if (int.TryParse(e.GetArg("index")?.Trim() ?? "", out index)) - { - index = index - 1; - if (index < 0 || index > NadekoBot.Config.CustomReactions[name].Count) - { - await e.Channel.SendMessage("Given index was out of range").ConfigureAwait(false); - return; - - } - NadekoBot.Config.CustomReactions[name].RemoveAt(index); - if (!NadekoBot.Config.CustomReactions[name].Any()) - { - NadekoBot.Config.CustomReactions.Remove(name); - } - message = $"Deleted response #{index + 1} from `{name}`"; - } - else - { - NadekoBot.Config.CustomReactions.Remove(name); - message = $"Deleted custom reaction: `{name}`"; - } - await Classes.JSONModels.ConfigHandler.SaveConfig().ConfigureAwait(false); - await e.Channel.SendMessage(message).ConfigureAwait(false); - }); - } - - private readonly int ItemsPerPage = 30; - - private IEnumerable GetCustomsOnPage(int page) - { - var items = NadekoBot.Config.CustomReactions.Skip(page * ItemsPerPage).Take(ItemsPerPage); - if (!items.Any()) - { - return Enumerable.Empty(); - } - return items.Select(kvp => kvp.Key); - /* - var message = new StringBuilder($"--- Custom reactions - page {page + 1} ---\n"); - foreach (var cr in items) - { - message.Append($"{Format.Code(cr.Key)}\n"); - int i = 1; - var last = cr.Value.Last(); - foreach (var reaction in cr.Value) - { - if (last != reaction) - message.AppendLine(" `├" + i++ + "─`" + Format.Bold(reaction)); - else - message.AppendLine(" `└" + i++ + "─`" + Format.Bold(reaction)); - } - } - return message.ToString() + "\n"; - */ - } - } -} -// zeta is a god -//├ -//─ -//│ -//└ diff --git a/NadekoBot/Modules/Administration/Commands/IncidentsCommands.cs b/NadekoBot/Modules/Administration/Commands/IncidentsCommands.cs deleted file mode 100644 index ab93693a..00000000 --- a/NadekoBot/Modules/Administration/Commands/IncidentsCommands.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.DataModels; -using NadekoBot.Modules.Permissions.Classes; -using System.IO; -using System.Linq; - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class IncidentsCommands : DiscordCommand - { - public IncidentsCommands(DiscordModule module) : base(module) { } - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "listincidents") - .Alias(Prefix + "lin") - .Description($"List all UNREAD incidents and flags them as read. **Needs Manage Server Permissions.**| `{Prefix}lin`") - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - var sid = (long)e.Server.Id; - var incs = DbHandler.Instance.FindAll(i => i.ServerId == sid && i.Read == false); - DbHandler.Instance.Connection.UpdateAll(incs.Select(i => { i.Read = true; return i; })); - - await e.User.SendMessage(string.Join("\n----------------------", incs.Select(i => i.Text))); - }); - - cgb.CreateCommand(Module.Prefix + "listallincidents") - .Alias(Prefix + "lain") - .Description($"Sends you a file containing all incidents and flags them as read. **Needs Manage Server Permissions.**| `{Prefix}lain`") - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - var sid = (long)e.Server.Id; - var incs = DbHandler.Instance.FindAll(i => i.ServerId == sid); - DbHandler.Instance.Connection.UpdateAll(incs.Select(i => { i.Read = true; return i; })); - var data = string.Join("\n----------------------\n", incs.Select(i => i.Text)); - MemoryStream ms = new MemoryStream(); - var sw = new StreamWriter(ms); - sw.WriteLine(data); - sw.Flush(); - sw.BaseStream.Position = 0; - await e.User.SendFile("incidents.txt", sw.BaseStream); - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs deleted file mode 100644 index 21973116..00000000 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ /dev/null @@ -1,489 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class LogCommand : DiscordCommand - { - private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; - - private ConcurrentBag> voicePresenceUpdates = new ConcurrentBag>(); - - public LogCommand(DiscordModule module) : base(module) - { - NadekoBot.OnReady += () => - { - //NadekoBot.Client.MessageReceived += MsgRecivd; - NadekoBot.Client.MessageDeleted += MsgDltd; - NadekoBot.Client.MessageUpdated += MsgUpdtd; - NadekoBot.Client.UserUpdated += UsrUpdtd; - NadekoBot.Client.UserBanned += UsrBanned; - NadekoBot.Client.UserLeft += UsrLeft; - NadekoBot.Client.UserJoined += UsrJoined; - NadekoBot.Client.UserUnbanned += UsrUnbanned; - NadekoBot.Client.ChannelCreated += ChannelCreated; - NadekoBot.Client.ChannelDestroyed += ChannelDestroyed; - NadekoBot.Client.ChannelUpdated += ChannelUpdated; - - - NadekoBot.Client.MessageReceived += async (s, e) => - { - if (e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) - return; - if (!SpecificConfigurations.Default.Of(e.Server.Id).SendPrivateMessageOnMention) return; - try - { - var usr = e.Message.MentionedUsers.FirstOrDefault(u => u != e.User); - if (usr?.Status != UserStatus.Offline) - return; - await e.Channel.SendMessage($"User `{usr.Name}` is offline. PM sent.").ConfigureAwait(false); - await usr.SendMessage( - $"User `{e.User.Name}` mentioned you on " + - $"`{e.Server.Name}` server while you were offline.\n" + - $"`Message:` {e.Message.Text}").ConfigureAwait(false); - } - catch { } - }; - }; - - // start the userpresence queue - - NadekoBot.OnReady += () => - { - Task.Run(async () => - { - while (true) - { - var toSend = new Dictionary(); - //take everything from the queue and merge the messages which are going to the same channel - KeyValuePair item; - while (voicePresenceUpdates.TryTake(out item)) - { - if (toSend.ContainsKey(item.Key)) - { - toSend[item.Key] = toSend[item.Key] + Environment.NewLine + item.Value; - } - else - { - toSend.Add(item.Key, item.Value); - } - } - //send merged messages to each channel - foreach (var k in toSend) - { - try { await k.Key.SendMessage(Environment.NewLine + k.Value).ConfigureAwait(false); } catch { } - } - - await Task.Delay(5000); - } - }); - }; - } - - private async void ChannelUpdated(object sender, ChannelUpdatedEventArgs e) - { - try - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - var chId = config.LogServerChannel; - if (chId == null || config.LogserverIgnoreChannels.Contains(e.After.Id)) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - if (e.Before.Name != e.After.Name) - await ch.SendMessage($@"`{prettyCurrentTime}` **Channel Name Changed** `#{e.Before.Name}` (*{e.After.Id}*) - `New:` {e.After.Name}").ConfigureAwait(false); - else if (e.Before.Topic != e.After.Topic) - await ch.SendMessage($@"`{prettyCurrentTime}` **Channel Topic Changed** `#{e.After.Name}` (*{e.After.Id}*) - `Old:` {e.Before.Topic} - `New:` {e.After.Topic}").ConfigureAwait(false); - } - catch { } - } - - private async void ChannelDestroyed(object sender, ChannelEventArgs e) - { - try - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - var chId = config.LogServerChannel; - if (chId == null || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await ch.SendMessage($"❗`{prettyCurrentTime}`❗`Channel Deleted:` #{e.Channel.Name} (*{e.Channel.Id}*)").ConfigureAwait(false); - } - catch { } - } - - private async void ChannelCreated(object sender, ChannelEventArgs e) - { - try - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - var chId = config.LogServerChannel; - if (chId == null || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await ch.SendMessage($"`{prettyCurrentTime}`🆕`Channel Created:` #{e.Channel.Mention} (*{e.Channel.Id}*)").ConfigureAwait(false); - } - catch { } - } - - private async void UsrUnbanned(object sender, UserEventArgs e) - { - try - { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await ch.SendMessage($"`{prettyCurrentTime}`♻`User was unbanned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); - } - catch { } - } - - private async void UsrJoined(object sender, UserEventArgs e) - { - try - { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await ch.SendMessage($"`{prettyCurrentTime}`✅`User joined:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); - } - catch { } - } - - private async void UsrLeft(object sender, UserEventArgs e) - { - try - { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await ch.SendMessage($"`{prettyCurrentTime}`❗`User left:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); - } - catch { } - } - - private async void UsrBanned(object sender, UserEventArgs e) - { - try - { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await ch.SendMessage($"❗`{prettyCurrentTime}`❌`User banned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); - } - catch { } - } - - // private async void MsgRecivd(object sender, MessageEventArgs e) - // { - // try - // { - // if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) - // return; - // var config = SpecificConfigurations.Default.Of(e.Server.Id); - // var chId = config.LogServerChannel; - // if (chId == null || e.Channel.Id == chId || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) - // return; - // Channel ch; - // if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - // return; - // if (!string.IsNullOrWhiteSpace(e.Message.Text)) - // { - // await ch.SendMessage( - // $@"🕔`{prettyCurrentTime}` **New Message** `#{e.Channel.Name}` - //👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Text.Unmention()}").ConfigureAwait(false); - // } - // else - // { - // await ch.SendMessage( - // $@"🕔`{prettyCurrentTime}` **File Uploaded** `#{e.Channel.Name}` - //👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Attachments.FirstOrDefault()?.ProxyUrl}").ConfigureAwait(false); - // } - - // } - // catch { } - // } - private async void MsgDltd(object sender, MessageEventArgs e) - { - try - { - if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) - return; - var config = SpecificConfigurations.Default.Of(e.Server.Id); - var chId = config.LogServerChannel; - if (chId == null || e.Channel.Id == chId || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - if (!string.IsNullOrWhiteSpace(e.Message.Text)) - { - await ch.SendMessage( - $@"🕔`{prettyCurrentTime}` **Message** 🚮 `#{e.Channel.Name}` -👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Text.Unmention()}").ConfigureAwait(false); - } - else - { - await ch.SendMessage( - $@"🕔`{prettyCurrentTime}` **File Deleted** `#{e.Channel.Name}` -👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Attachments.FirstOrDefault()?.ProxyUrl}").ConfigureAwait(false); - } - } - catch { } - } - private async void MsgUpdtd(object sender, MessageUpdatedEventArgs e) - { - try - { - if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id) - return; - var config = SpecificConfigurations.Default.Of(e.Server.Id); - var chId = config.LogServerChannel; - if (chId == null || e.Channel.Id == chId || config.LogserverIgnoreChannels.Contains(e.Channel.Id)) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - await ch.SendMessage( - $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}` -👤`{e.User?.ToString() ?? ("NULL")}` - `Old:` {e.Before.Text.Unmention()} - `New:` {e.After.Text.Unmention()}").ConfigureAwait(false); - } - catch { } - } - private async void UsrUpdtd(object sender, UserUpdatedEventArgs e) - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - try - { - var chId = config.LogPresenceChannel; - if (chId != null) - { - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) != null) - { - if (e.Before.Status != e.After.Status) - { - voicePresenceUpdates.Add(new KeyValuePair(ch, $"`{prettyCurrentTime}`**{e.Before.Name}** is now **{e.After.Status}**.")); - } - } - } - } - catch { } - - try - { - ulong notifyChBeforeId; - ulong notifyChAfterId; - Channel notifyChBefore = null; - Channel notifyChAfter = null; - var beforeVch = e.Before.VoiceChannel; - var afterVch = e.After.VoiceChannel; - var notifyLeave = false; - var notifyJoin = false; - if ((beforeVch != null || afterVch != null) && (beforeVch != afterVch)) // this means we need to notify for sure. - { - if (beforeVch != null && config.VoiceChannelLog.TryGetValue(beforeVch.Id, out notifyChBeforeId) && (notifyChBefore = e.Before.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChBeforeId)) != null) - { - notifyLeave = true; - } - if (afterVch != null && config.VoiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null) - { - notifyJoin = true; - } - if ((notifyLeave && notifyJoin) && (notifyChAfter == notifyChBefore)) - { - await notifyChAfter.SendMessage($"🎼`{prettyCurrentTime}` {e.Before.Name} moved from **{beforeVch.Mention}** to **{afterVch.Mention}** voice channel.").ConfigureAwait(false); - } - else if (notifyJoin) - { - await notifyChAfter.SendMessage($"🎼`{prettyCurrentTime}` {e.Before.Name} has joined **{afterVch.Mention}** voice channel.").ConfigureAwait(false); - } - else if (notifyLeave) - { - await notifyChBefore.SendMessage($"🎼`{prettyCurrentTime}` {e.Before.Name} has left **{beforeVch.Mention}** voice channel.").ConfigureAwait(false); - } - } - } - catch { } - - try - { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) - return; - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - string str = $"🕔`{prettyCurrentTime}`"; - if (e.Before.Name != e.After.Name) - str += $"**Name Changed**👤`{e.Before?.ToString()}`\n\t\t`New:`{e.After.ToString()}`"; - else if (e.Before.Nickname != e.After.Nickname) - str += $"**Nickname Changed**👤`{e.Before?.ToString()}`\n\t\t`Old:` {e.Before.Nickname}#{e.Before.Discriminator}\n\t\t`New:` {e.After.Nickname}#{e.After.Discriminator}"; - else if (e.Before.AvatarUrl != e.After.AvatarUrl) - str += $"**Avatar Changed**👤`{e.Before?.ToString()}`\n\t {await e.Before.AvatarUrl.ShortenUrl()} `=>` {await e.After.AvatarUrl.ShortenUrl()}"; - else if (!e.Before.Roles.SequenceEqual(e.After.Roles)) - { - if (e.Before.Roles.Count() < e.After.Roles.Count()) - { - var diffRoles = e.After.Roles.Where(r => !e.Before.Roles.Contains(r)).Select(r => "`" + r.Name + "`"); - str += $"**User's Roles changed ⚔➕**👤`{e.Before?.ToString()}`\n\tNow has {string.Join(", ", diffRoles)} role."; - } - else if (e.Before.Roles.Count() > e.After.Roles.Count()) - { - var diffRoles = e.Before.Roles.Where(r => !e.After.Roles.Contains(r)).Select(r => "`" + r.Name + "`"); - str += $"**User's Roles changed ⚔➖**👤`{e.Before?.ToString()}`\n\tNo longer has {string.Join(", ", diffRoles)} role."; - } - else - { - Console.WriteLine("SEQUENCE NOT EQUAL BUT NO DIFF ROLES - REPORT TO KWOTH on #NADEKOLOG server"); - return; - } - - } - else - return; - await ch.SendMessage(str).ConfigureAwait(false); - } - catch { } - } - - internal override void Init(CommandGroupBuilder cgb) - { - - cgb.CreateCommand(Module.Prefix + "spmom") - .Description($"Toggles whether mentions of other offline users on your server will send a pm to them. **Needs Manage Server Permissions.**| `{Prefix}spmom`") - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - var specificConfig = SpecificConfigurations.Default.Of(e.Server.Id); - specificConfig.SendPrivateMessageOnMention = - !specificConfig.SendPrivateMessageOnMention; - if (specificConfig.SendPrivateMessageOnMention) - await e.Channel.SendMessage(":ok: I will send private messages " + - "to mentioned offline users.").ConfigureAwait(false); - else - await e.Channel.SendMessage(":ok: I won't send private messages " + - "to mentioned offline users anymore.").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "logserver") - .Description($"Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!** | `{Prefix}logserver`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel; - if (chId == null) - { - SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id; - await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false); - return; - } - Channel ch; - if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null) - return; - - SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = null; - await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); - }); - - - cgb.CreateCommand(Prefix + "logignore") - .Description($"Toggles whether the {Prefix}logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Bot Owner Only!**| `{Prefix}logignore`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - if (config.LogserverIgnoreChannels.Remove(e.Channel.Id)) - { - await e.Channel.SendMessage($"`{Prefix}logserver will stop ignoring this channel.`"); - } - else - { - config.LogserverIgnoreChannels.Add(e.Channel.Id); - await e.Channel.SendMessage($"`{Prefix}logserver will ignore this channel.`"); - } - }); - - cgb.CreateCommand(Module.Prefix + "userpresence") - .Description($"Starts logging to this channel when someone from the server goes online/offline/idle. **Needs Manage Server Permissions.**| `{Prefix}userpresence`") - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel; - if (chId == null) - { - SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel = e.Channel.Id; - await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); - return; - } - SpecificConfigurations.Default.Of(e.Server.Id).LogPresenceChannel = null; - await e.Channel.SendMessage($"**User presence notifications disabled.**").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "voicepresence") - .Description($"Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. **Needs Manage Server Permissions.**| `{Prefix}voicerpresence`") - .Parameter("all", ParameterType.Optional) - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - - var config = SpecificConfigurations.Default.Of(e.Server.Id); - if (e.GetArg("all")?.ToLower() == "all") - { - foreach (var voiceChannel in e.Server.VoiceChannels) - { - config.VoiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id); - } - await e.Channel.SendMessage("Started logging user presence for **ALL** voice channels!").ConfigureAwait(false); - return; - } - - if (e.User.VoiceChannel == null) - { - await e.Channel.SendMessage("💢 You are not in a voice channel right now. If you are, please rejoin it.").ConfigureAwait(false); - return; - } - ulong throwaway; - if (!config.VoiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway)) - { - config.VoiceChannelLog.TryAdd(e.User.VoiceChannel.Id, e.Channel.Id); - await e.Channel.SendMessage($"`Logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false); - } - else - await e.Channel.SendMessage($"`Stopped logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false); - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs b/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs deleted file mode 100644 index e87f5555..00000000 --- a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; -using System.Timers; - -namespace NadekoBot.Modules.Administration.Commands -{ - class MessageRepeater : DiscordCommand - { - private readonly ConcurrentDictionary repeaters = new ConcurrentDictionary(); - private class Repeater - { - [Newtonsoft.Json.JsonIgnore] - public Timer MessageTimer { get; set; } - [Newtonsoft.Json.JsonIgnore] - public Channel RepeatingChannel { get; set; } - - public ulong RepeatingServerId { get; set; } - public ulong RepeatingChannelId { get; set; } - public Message lastMessage { get; set; } = null; - public string RepeatingMessage { get; set; } - public int Interval { get; set; } - - public Repeater Start() - { - MessageTimer = new Timer { Interval = Interval }; - MessageTimer.Elapsed += async (s, e) => await Invoke(); - return this; - } - - public async Task Invoke() - { - var ch = RepeatingChannel; - var msg = RepeatingMessage; - if (ch != null && !string.IsNullOrWhiteSpace(msg)) - { - try - { - if (lastMessage != null) - await lastMessage.Delete().ConfigureAwait(false); - } - catch { } - try - { - lastMessage = await ch.SendMessage(msg).ConfigureAwait(false); - } - catch { } - } - } - } - internal override void Init(CommandGroupBuilder cgb) - { - - cgb.CreateCommand(Module.Prefix + "repeatinvoke") - .Alias(Module.Prefix + "repinv") - .Description($"Immediately shows the repeat message and restarts the timer. **Needs Manage Messages Permissions.**| `{Prefix}repinv`") - .AddCheck(SimpleCheckers.ManageMessages()) - .Do(async e => - { - Repeater rep; - if (!repeaters.TryGetValue(e.Server, out rep)) - { - await e.Channel.SendMessage("`No repeating message found on this server.`"); - return; - } - - await rep.Invoke(); - }); - - cgb.CreateCommand(Module.Prefix + "repeat") - .Description("Repeat a message every X minutes. If no parameters are specified, " + - $"repeat is disabled. **Needs Manage Messages Permissions.** |`{Prefix}repeat 5 Hello there`") - .Parameter("minutes", ParameterType.Optional) - .Parameter("msg", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.ManageMessages()) - .Do(async e => - { - var minutesStr = e.GetArg("minutes"); - var msg = e.GetArg("msg"); - - // if both null, disable - if (string.IsNullOrWhiteSpace(msg) && string.IsNullOrWhiteSpace(minutesStr)) - { - - Repeater rep; - if (!repeaters.TryRemove(e.Server, out rep)) - return; - rep.MessageTimer.Stop(); - await e.Channel.SendMessage("Repeating disabled").ConfigureAwait(false); - return; - } - int minutes; - if (!int.TryParse(minutesStr, out minutes) || minutes < 1 || minutes > 1440) - { - await e.Channel.SendMessage("Invalid value").ConfigureAwait(false); - return; - } - - var repeater = repeaters.GetOrAdd( - e.Server, - s => new Repeater - { - Interval = minutes * 60 * 1000, - RepeatingChannel = e.Channel, - RepeatingChannelId = e.Channel.Id, - RepeatingServerId = e.Server.Id, - }.Start() - ); - - if (!string.IsNullOrWhiteSpace(msg)) - repeater.RepeatingMessage = msg; - - repeater.MessageTimer.Stop(); - repeater.MessageTimer.Start(); - - await e.Channel.SendMessage(String.Format("👌 Repeating `{0}` every " + - "**{1}** minutes on {2} channel.", - repeater.RepeatingMessage, minutes, repeater.RepeatingChannel)) - .ConfigureAwait(false); - }); - } - - public MessageRepeater(DiscordModule module) : base(module) { } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs deleted file mode 100644 index 3eff2ff2..00000000 --- a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Modules.Music; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Timer = System.Timers.Timer; - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class PlayingRotate : DiscordCommand - { - private static readonly Timer timer = new Timer(20000); - - public static Dictionary> PlayingPlaceholders { get; } = - new Dictionary> { - {"%servers%", () => NadekoBot.Client.Servers.Count().ToString()}, - {"%users%", () => NadekoBot.Client.Servers.SelectMany(s => s.Users).Count().ToString()}, - {"%playing%", () => { - var cnt = MusicModule.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null); - if (cnt != 1) return cnt.ToString(); - try { - var mp = MusicModule.MusicPlayers.FirstOrDefault(); - return mp.Value.CurrentSong.SongInfo.Title; - } - catch { - return "No songs"; - } - } - }, - {"%queued%", () => MusicModule.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()}, - {"%trivia%", () => Games.Commands.TriviaCommands.RunningTrivias.Count.ToString()} - }; - - private readonly SemaphoreSlim playingPlaceholderLock = new SemaphoreSlim(1, 1); - - public PlayingRotate(DiscordModule module) : base(module) - { - var i = -1; - timer.Elapsed += async (s, e) => - { - try - { - i++; - var status = ""; - //wtf am i doing, just use a queue ffs - await playingPlaceholderLock.WaitAsync().ConfigureAwait(false); - try - { - if (PlayingPlaceholders.Count == 0 - || NadekoBot.Config.RotatingStatuses.Count == 0 - || i >= NadekoBot.Config.RotatingStatuses.Count) - { - i = 0; - } - status = NadekoBot.Config.RotatingStatuses[i]; - status = PlayingPlaceholders.Aggregate(status, - (current, kvp) => current.Replace(kvp.Key, kvp.Value())); - } - finally { playingPlaceholderLock.Release(); } - if (string.IsNullOrWhiteSpace(status)) - return; - await Task.Run(() => { NadekoBot.Client.SetGame(status); }); - } - catch { } - }; - NadekoBot.OnReady += () => timer.Enabled = NadekoBot.Config.IsRotatingStatus; - } - - public Func DoFunc() => async e => - { - await playingPlaceholderLock.WaitAsync().ConfigureAwait(false); - try - { - if (timer.Enabled) - timer.Stop(); - else - timer.Start(); - NadekoBot.Config.IsRotatingStatus = timer.Enabled; - await ConfigHandler.SaveConfig().ConfigureAwait(false); - } - finally - { - playingPlaceholderLock.Release(); - } - await e.Channel.SendMessage($"❗`Rotating playing status has been {(timer.Enabled ? "enabled" : "disabled")}.`").ConfigureAwait(false); - }; - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "rotateplaying") - .Alias(Module.Prefix + "ropl") - .Description($"Toggles rotation of playing status of the dynamic strings you specified earlier. **Bot Owner Only!** | `{Prefix}ropl`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(DoFunc()); - - cgb.CreateCommand(Module.Prefix + "addplaying") - .Alias(Module.Prefix + "adpl") - .Description("Adds a specified string to the list of playing strings to rotate. " + - "Supported placeholders: " + string.Join(", ", PlayingPlaceholders.Keys) + $" **Bot Owner Only!**| `{Prefix}adpl`") - .Parameter("text", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var arg = e.GetArg("text"); - if (string.IsNullOrWhiteSpace(arg)) - return; - await playingPlaceholderLock.WaitAsync().ConfigureAwait(false); - try - { - NadekoBot.Config.RotatingStatuses.Add(arg); - await ConfigHandler.SaveConfig(); - } - finally - { - playingPlaceholderLock.Release(); - } - await e.Channel.SendMessage("🆗 `Added a new playing string.`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "listplaying") - .Alias(Module.Prefix + "lipl") - .Description($"Lists all playing statuses with their corresponding number. **Bot Owner Only!**| `{Prefix}lipl`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - if (NadekoBot.Config.RotatingStatuses.Count == 0) - await e.Channel.SendMessage("`There are no playing strings. " + - "Add some with .addplaying [text] command.`").ConfigureAwait(false); - var sb = new StringBuilder(); - for (var i = 0; i < NadekoBot.Config.RotatingStatuses.Count; i++) - { - sb.AppendLine($"`{i + 1}.` {NadekoBot.Config.RotatingStatuses[i]}"); - } - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "removeplaying") - .Alias(Module.Prefix + "repl", Module.Prefix + "rmpl") - .Description($"Removes a playing string on a given number. **Bot Owner Only!**| `{Prefix}rmpl`") - .Parameter("number", ParameterType.Required) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var arg = e.GetArg("number"); - int num; - string str; - await playingPlaceholderLock.WaitAsync().ConfigureAwait(false); - try - { - if (!int.TryParse(arg.Trim(), out num) || num <= 0 || num > NadekoBot.Config.RotatingStatuses.Count) - return; - str = NadekoBot.Config.RotatingStatuses[num - 1]; - NadekoBot.Config.RotatingStatuses.RemoveAt(num - 1); - await ConfigHandler.SaveConfig().ConfigureAwait(false); - } - finally { playingPlaceholderLock.Release(); } - await e.Channel.SendMessage($"🆗 `Removed playing string #{num}`({str})").ConfigureAwait(false); - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs b/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs deleted file mode 100644 index 38c30d07..00000000 --- a/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Concurrent; - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class RatelimitCommand : DiscordCommand - { - - public static ConcurrentDictionary> RatelimitingChannels = new ConcurrentDictionary>(); - - private static readonly TimeSpan ratelimitTime = new TimeSpan(0, 0, 0, 5); - - public RatelimitCommand(DiscordModule module) : base(module) - { - NadekoBot.OnReady += () => NadekoBot.Client.MessageReceived += async (s, e) => - { - if (e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) - return; - ConcurrentDictionary userTimePair; - if (!RatelimitingChannels.TryGetValue(e.Channel.Id, out userTimePair)) return; - DateTime lastMessageTime; - if (userTimePair.TryGetValue(e.User.Id, out lastMessageTime)) - { - if (DateTime.Now - lastMessageTime < ratelimitTime) - { - try - { - await e.Message.Delete().ConfigureAwait(false); - } - catch { } - return; - } - } - userTimePair.AddOrUpdate(e.User.Id, id => DateTime.Now, (id, dt) => DateTime.Now); - }; - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "slowmode") - .Description($"Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. **Needs Manage Messages Permissions.**| `{Prefix}slowmode`") - .AddCheck(SimpleCheckers.ManageMessages()) - .Do(async e => - { - ConcurrentDictionary throwaway; - if (RatelimitingChannels.TryRemove(e.Channel.Id, out throwaway)) - { - await e.Channel.SendMessage("Slow mode disabled.").ConfigureAwait(false); - return; - } - if (RatelimitingChannels.TryAdd(e.Channel.Id, new ConcurrentDictionary())) - { - await e.Channel.SendMessage("Slow mode initiated. " + - "Users can't send more than 1 message every 5 seconds.") - .ConfigureAwait(false); - } - }); - } - } -} \ No newline at end of file diff --git a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs deleted file mode 100644 index 8e5e54fb..00000000 --- a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs +++ /dev/null @@ -1,207 +0,0 @@ -using Discord.Commands; -using Discord.Net; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class SelfAssignedRolesCommand : DiscordCommand - { - public SelfAssignedRolesCommand(DiscordModule module) : base(module) { } - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "asar") - .Description("Adds a role, or list of roles separated by whitespace" + - $"(use quotations for multiword roles) to the list of self-assignable roles. **Needs Manage Roles Permissions.**| `{Prefix}asar Gamer`") - .Parameter("roles", ParameterType.Multiple) - .AddCheck(SimpleCheckers.CanManageRoles) - .Do(async e => - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - var msg = new StringBuilder(); - foreach (var arg in e.Args) - { - var role = e.Server.FindRoles(arg.Trim()).FirstOrDefault(); - if (role == null) - msg.AppendLine($":anger:Role **{arg}** not found."); - else - { - if (config.ListOfSelfAssignableRoles.Contains(role.Id)) - { - msg.AppendLine($":anger:Role **{role.Name}** is already in the list."); - continue; - } - config.ListOfSelfAssignableRoles.Add(role.Id); - msg.AppendLine($":ok:Role **{role.Name}** added to the list."); - } - } - await e.Channel.SendMessage(msg.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "rsar") - .Description($"Removes a specified role from the list of self-assignable roles. | `{Prefix}rsar`") - .Parameter("role", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.CanManageRoles) - .Do(async e => - { - var roleName = e.GetArg("role")?.Trim(); - if (string.IsNullOrWhiteSpace(roleName)) - return; - var role = e.Server.FindRoles(roleName).FirstOrDefault(); - if (role == null) - { - await e.Channel.SendMessage(":anger:That role does not exist.").ConfigureAwait(false); - return; - } - var config = SpecificConfigurations.Default.Of(e.Server.Id); - if (!config.ListOfSelfAssignableRoles.Contains(role.Id)) - { - await e.Channel.SendMessage(":anger:That role is not self-assignable.").ConfigureAwait(false); - return; - } - config.ListOfSelfAssignableRoles.Remove(role.Id); - await e.Channel.SendMessage($":ok:**{role.Name}** has been removed from the list of self-assignable roles").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "lsar") - .Description($"Lists all self-assignable roles. | `{Prefix}lsar`") - .Parameter("roles", ParameterType.Multiple) - .Do(async e => - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - var msg = new StringBuilder($"There are `{config.ListOfSelfAssignableRoles.Count}` self assignable roles:\n"); - var toRemove = new HashSet(); - foreach (var roleId in config.ListOfSelfAssignableRoles.OrderBy(r => r.ToString())) - { - var role = e.Server.GetRole(roleId); - if (role == null) - { - msg.Append($"`{roleId} not found. Cleaned up.`, "); - toRemove.Add(roleId); - } - else - { - msg.Append($"**{role.Name}**, "); - } - } - foreach (var id in toRemove) - { - config.ListOfSelfAssignableRoles.Remove(id); - } - await e.Channel.SendMessage(msg.ToString()).ConfigureAwait(false); - }); - - - - cgb.CreateCommand(Module.Prefix + "togglexclsar").Alias(Module.Prefix + "tesar") - .Description($"toggle whether the self-assigned roles should be exclusive | `{Prefix}tesar`") - .AddCheck(SimpleCheckers.CanManageRoles) - .Do(async e => - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles; - string exl = config.ExclusiveSelfAssignedRoles ? "exclusive" : "not exclusive"; - await e.Channel.SendMessage("Self assigned roles are now " + exl); - }); - - cgb.CreateCommand(Module.Prefix + "iam") - .Description("Adds a role to you that you choose. " + - "Role must be on a list of self-assignable roles." + - $" | `{Prefix}iam Gamer`") - .Parameter("role", ParameterType.Unparsed) - .Do(async e => - { - var roleName = e.GetArg("role")?.Trim(); - if (string.IsNullOrWhiteSpace(roleName)) - return; - var role = e.Server.FindRoles(roleName).FirstOrDefault(); - if (role == null) - { - await e.Channel.SendMessage(":anger:That role does not exist.").ConfigureAwait(false); - return; - } - var config = SpecificConfigurations.Default.Of(e.Server.Id); - if (!config.ListOfSelfAssignableRoles.Contains(role.Id)) - { - await e.Channel.SendMessage(":anger:That role is not self-assignable.").ConfigureAwait(false); - return; - } - if (e.User.HasRole(role)) - { - await e.Channel.SendMessage($":anger:You already have {role.Name} role.").ConfigureAwait(false); - return; - } - var sameRoles = e.User.Roles.Where(r => config.ListOfSelfAssignableRoles.Contains(r.Id)); - if (config.ExclusiveSelfAssignedRoles && sameRoles.Any()) - { - await e.Channel.SendMessage($":anger:You already have {sameRoles.FirstOrDefault().Name} role.").ConfigureAwait(false); - return; - } - try - { - await e.User.AddRoles(role).ConfigureAwait(false); - } - catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.InternalServerError) - { - } - catch (Exception ex) - { - await e.Channel.SendMessage($":anger:`I am unable to add that role to you. I can't add roles to owners or other roles higher than my role in the role hierarchy.`").ConfigureAwait(false); - return; - } - var msg = await e.Channel.SendMessage($":ok:You now have {role.Name} role.").ConfigureAwait(false); - await Task.Delay(3000).ConfigureAwait(false); - await msg.Delete().ConfigureAwait(false); - try - { - await e.Message.Delete().ConfigureAwait(false); - } - catch { } - }); - - cgb.CreateCommand(Module.Prefix + "iamnot") - .Alias(Module.Prefix + "iamn") - .Description("Removes a role to you that you choose. " + - "Role must be on a list of self-assignable roles." + - $" | `{Prefix}iamn Gamer`") - .Parameter("role", ParameterType.Unparsed) - .Do(async e => - { - var roleName = e.GetArg("role")?.Trim(); - if (string.IsNullOrWhiteSpace(roleName)) - return; - var role = e.Server.FindRoles(roleName).FirstOrDefault(); - if (role == null) - { - await e.Channel.SendMessage(":anger:That role does not exist.").ConfigureAwait(false); - return; - } - var config = SpecificConfigurations.Default.Of(e.Server.Id); - if (!config.ListOfSelfAssignableRoles.Contains(role.Id)) - { - await e.Channel.SendMessage(":anger:That role is not self-assignable.").ConfigureAwait(false); - return; - } - if (!e.User.HasRole(role)) - { - await e.Channel.SendMessage($":anger:You don't have {role.Name} role.").ConfigureAwait(false); - return; - } - await e.User.RemoveRoles(role).ConfigureAwait(false); - var msg = await e.Channel.SendMessage($":ok:Successfuly removed {role.Name} role from you.").ConfigureAwait(false); - await Task.Delay(3000).ConfigureAwait(false); - await msg.Delete().ConfigureAwait(false); - try - { - await e.Message.Delete().ConfigureAwait(false); - } - catch { } - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/SelfCommands.cs b/NadekoBot/Modules/Administration/Commands/SelfCommands.cs deleted file mode 100644 index f8019ac9..00000000 --- a/NadekoBot/Modules/Administration/Commands/SelfCommands.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System.Linq; - -namespace NadekoBot.Modules.Administration.Commands -{ - class SelfCommands : DiscordCommand - { - public SelfCommands(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "leave") - .Description($"Makes Nadeko leave the server. Either name or id required. **Bot Owner Only!**| `{Prefix}leave 123123123331`") - .Parameter("arg", ParameterType.Required) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var arg = e.GetArg("arg").Trim(); - var server = NadekoBot.Client.Servers.FirstOrDefault(s => s.Id.ToString() == arg) ?? - NadekoBot.Client.FindServers(arg).FirstOrDefault(); - if (server == null) - { - await e.Channel.SendMessage("Cannot find that server").ConfigureAwait(false); - return; - } - if (!server.IsOwner) - { - await server.Leave().ConfigureAwait(false); - } - else - { - await server.Delete().ConfigureAwait(false); - } - await NadekoBot.SendMessageToOwner("Left server " + server.Name).ConfigureAwait(false); - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs b/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs deleted file mode 100644 index c4ebf22c..00000000 --- a/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs +++ /dev/null @@ -1,336 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; - -/* Voltana's legacy -public class AsyncLazy : Lazy> -{ - public AsyncLazy(Func valueFactory) : - base(() => Task.Factory.StartNew(valueFactory)) { } - - public AsyncLazy(Func> taskFactory) : - base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap()) { } - - public TaskAwaiter GetAwaiter() { return Value.GetAwaiter(); } -} -*/ - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class ServerGreetCommand : DiscordCommand - { - - public static ConcurrentDictionary AnnouncementsDictionary; - - public static long Greeted = 0; - - public ServerGreetCommand(DiscordModule module) : base(module) - { - AnnouncementsDictionary = new ConcurrentDictionary(); - - //gotta subscribe after ready, to prevent trying to send these before all guilds are initialized - NadekoBot.OnReady += () => - { - NadekoBot.Client.UserJoined += UserJoined; - NadekoBot.Client.UserLeft += UserLeft; - }; - - var data = Classes.DbHandler.Instance.GetAllRows(); - - if (!data.Any()) return; - foreach (var obj in data) - AnnouncementsDictionary.TryAdd((ulong)obj.ServerId, new AnnounceControls(obj)); - } - - private async void UserLeft(object sender, UserEventArgs e) - { - try - { - if (!AnnouncementsDictionary.ContainsKey(e.Server.Id) || - !AnnouncementsDictionary[e.Server.Id].Bye) return; - - var controls = AnnouncementsDictionary[e.Server.Id]; - var channel = NadekoBot.Client.GetChannel(controls.ByeChannel); - var msg = controls.ByeText.Replace("%user%", "**" + e.User.Name + "**").Trim(); - if (string.IsNullOrEmpty(msg)) - return; - - if (controls.ByePM) - { - Greeted++; - try - { - await e.User.SendMessage($"`Farewell Message From {e.Server?.Name}`\n" + msg).ConfigureAwait(false); - - } - catch { } - } - else - { - if (channel == null) return; - Greeted++; - var toDelete = await channel.SendMessage(msg).ConfigureAwait(false); - if (e.Server.CurrentUser.GetPermissions(channel).ManageMessages && controls.DeleteGreetMessages) - { - await Task.Delay(30000).ConfigureAwait(false); // 5 minutes - await toDelete.Delete().ConfigureAwait(false); - } - } - } - catch { } - } - - private async void UserJoined(object sender, Discord.UserEventArgs e) - { - try - { - if (!AnnouncementsDictionary.ContainsKey(e.Server.Id) || - !AnnouncementsDictionary[e.Server.Id].Greet) return; - - var controls = AnnouncementsDictionary[e.Server.Id]; - var channel = NadekoBot.Client.GetChannel(controls.GreetChannel); - - var msg = controls.GreetText.Replace("%user%", e.User.Mention).Trim(); - if (string.IsNullOrEmpty(msg)) - return; - if (controls.GreetPM) - { - Greeted++; - await e.User.SendMessage($"`Welcome Message From {e.Server.Name}`\n" + msg).ConfigureAwait(false); - } - else - { - if (channel == null) return; - Greeted++; - var toDelete = await channel.SendMessage(msg).ConfigureAwait(false); - if (e.Server.CurrentUser.GetPermissions(channel).ManageMessages && controls.DeleteGreetMessages) - { - await Task.Delay(30000).ConfigureAwait(false); // 5 minutes - await toDelete.Delete().ConfigureAwait(false); - } - } - } - catch { } - } - - public class AnnounceControls - { - private DataModels.Announcement _model { get; } - - public bool Greet { - get { return _model.Greet; } - set { _model.Greet = value; Save(); } - } - - public ulong GreetChannel { - get { return (ulong)_model.GreetChannelId; } - set { _model.GreetChannelId = (long)value; Save(); } - } - - public bool GreetPM { - get { return _model.GreetPM; } - set { _model.GreetPM = value; Save(); } - } - - public bool ByePM { - get { return _model.ByePM; } - set { _model.ByePM = value; Save(); } - } - - public string GreetText { - get { return _model.GreetText; } - set { _model.GreetText = value; Save(); } - } - - public bool Bye { - get { return _model.Bye; } - set { _model.Bye = value; Save(); } - } - public ulong ByeChannel { - get { return (ulong)_model.ByeChannelId; } - set { _model.ByeChannelId = (long)value; Save(); } - } - - public string ByeText { - get { return _model.ByeText; } - set { _model.ByeText = value; Save(); } - } - - public ulong ServerId { - get { return (ulong)_model.ServerId; } - set { _model.ServerId = (long)value; } - } - - public bool DeleteGreetMessages { - get { - return _model.DeleteGreetMessages; - } - set { - _model.DeleteGreetMessages = value; Save(); - } - } - - public AnnounceControls(DataModels.Announcement model) - { - this._model = model; - } - - public AnnounceControls(ulong serverId) - { - this._model = new DataModels.Announcement(); - ServerId = serverId; - } - - internal bool ToggleBye(ulong id) - { - if (Bye) - { - return Bye = false; - } - else - { - ByeChannel = id; - return Bye = true; - } - } - - internal bool ToggleGreet(ulong id) - { - if (Greet) - { - return Greet = false; - } - else - { - GreetChannel = id; - return Greet = true; - } - } - - internal bool ToggleDelete() => DeleteGreetMessages = !DeleteGreetMessages; - internal bool ToggleGreetPM() => GreetPM = !GreetPM; - internal bool ToggleByePM() => ByePM = !ByePM; - - private void Save() - { - Classes.DbHandler.Instance.Save(_model); - } - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "grdel") - .Description($"Toggles automatic deletion of greet and bye messages. **Needs Manage Server Permissions.**| `{Prefix}grdel`") - .Do(async e => - { - if (!e.User.ServerPermissions.ManageServer) return; - var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - if (ann.ToggleDelete()) - await e.Channel.SendMessage("`Automatic deletion of greet and bye messages has been enabled.`").ConfigureAwait(false); - else - await e.Channel.SendMessage("`Automatic deletion of greet and bye messages has been disabled.`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "greet") - .Description($"Toggles anouncements on the current channel when someone joins the server. **Needs Manage Server Permissions.**| `{Prefix}greet`") - .Do(async e => - { - if (!e.User.ServerPermissions.ManageServer) return; - var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - if (ann.ToggleGreet(e.Channel.Id)) - await e.Channel.SendMessage("Greet announcements enabled on this channel.").ConfigureAwait(false); - else - await e.Channel.SendMessage("Greet announcements disabled.").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "greetmsg") - .Description($"Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. **Needs Manage Server Permissions.**| `{Prefix}greetmsg Welcome, %user%.`") - .Parameter("msg", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.ManageServer) return; - var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - if (string.IsNullOrWhiteSpace(e.GetArg("msg"))) - { - await e.Channel.SendMessage("`Current greet message:` " + ann.GreetText); - return; - } - - - ann.GreetText = e.GetArg("msg"); - await e.Channel.SendMessage("New greet message set.").ConfigureAwait(false); - if (!ann.Greet) - await e.Channel.SendMessage("Enable greet messsages by typing `.greet`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "bye") - .Description($"Toggles anouncements on the current channel when someone leaves the server. | `{Prefix}bye`") - .Do(async e => - { - if (!e.User.ServerPermissions.ManageServer) return; - var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - if (ann.ToggleBye(e.Channel.Id)) - await e.Channel.SendMessage("Bye announcements enabled on this channel.").ConfigureAwait(false); - else - await e.Channel.SendMessage("Bye announcements disabled.").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "byemsg") - .Description($"Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. **Needs Manage Server Permissions.**| `{Prefix}byemsg %user% has left.`") - .Parameter("msg", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.ManageServer) return; - var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - if (string.IsNullOrWhiteSpace(e.GetArg("msg"))) - { - await e.Channel.SendMessage("`Current bye message:` " + ann.ByeText); - return; - } - - ann.ByeText = e.GetArg("msg"); - await e.Channel.SendMessage("New bye message set.").ConfigureAwait(false); - if (!ann.Bye) - await e.Channel.SendMessage("Enable bye messsages by typing `.bye`.").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "byepm") - .Description($"Toggles whether the good bye messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `{Prefix}byepm`") - .Do(async e => - { - if (!e.User.ServerPermissions.ManageServer) return; - var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - - if (ann.ToggleByePM()) - await e.Channel.SendMessage("Bye messages will be sent in a PM from now on.\n ⚠ Keep in mind this might fail if the user and the bot have no common servers after the user leaves.").ConfigureAwait(false); - else - await e.Channel.SendMessage("Bye messages will be sent in a bound channel from now on.").ConfigureAwait(false); - if (!ann.Bye) - await e.Channel.SendMessage("Enable bye messsages by typing `.bye`, and set the bye message using `.byemsg`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "greetpm") - .Description($"Toggles whether the greet messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `{Prefix}greetpm`") - .Do(async e => - { - if (!e.User.ServerPermissions.ManageServer) return; - - var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - if (ann.ToggleGreetPM()) - await e.Channel.SendMessage("Greet messages will be sent in a PM from now on.").ConfigureAwait(false); - else - await e.Channel.SendMessage("Greet messages will be sent in a bound channel from now on.").ConfigureAwait(false); - if (!ann.Greet) - await e.Channel.SendMessage("Enable greet messsages by typing `.greet`, and set the greet message using `.greetmsg`").ConfigureAwait(false); - }); - } - } -} diff --git a/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs b/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs deleted file mode 100644 index af220059..00000000 --- a/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using ChPermOverride = Discord.ChannelPermissionOverrides; - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class VoicePlusTextCommand : DiscordCommand - { - Regex channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled); - public VoicePlusTextCommand(DiscordModule module) : base(module) - { - // changing servers may cause bugs - NadekoBot.OnReady += () => NadekoBot.Client.UserUpdated += async (sender, e) => - { - try - { - if (e.Server == null) - return; - var config = SpecificConfigurations.Default.Of(e.Server.Id); - if (e.Before.VoiceChannel == e.After.VoiceChannel) return; - if (!config.VoicePlusTextEnabled) - return; - var serverPerms = e.Server.GetUser(NadekoBot.Client.CurrentUser.Id)?.ServerPermissions; - if (serverPerms == null) - return; - if (!serverPerms.Value.ManageChannels || !serverPerms.Value.ManageRoles) - { - - try - { - await e.Server.Owner.SendMessage( - "I don't have manage server and/or Manage Channels permission," + - $" so I cannot run voice+text on **{e.Server.Name}** server.").ConfigureAwait(false); - } - catch { } // meh - config.VoicePlusTextEnabled = false; - return; - } - - - var beforeVch = e.Before.VoiceChannel; - if (beforeVch != null) - { - var textChannel = - e.Server.FindChannels(GetChannelName(beforeVch.Name), ChannelType.Text).FirstOrDefault(); - if (textChannel != null) - await textChannel.AddPermissionsRule(e.Before, - new ChPermOverride(readMessages: PermValue.Deny, - sendMessages: PermValue.Deny)).ConfigureAwait(false); - } - var afterVch = e.After.VoiceChannel; - if (afterVch != null && e.Server.AFKChannel != afterVch) - { - var textChannel = e.Server.FindChannels( - GetChannelName(afterVch.Name), - ChannelType.Text) - .FirstOrDefault(); - if (textChannel == null) - { - textChannel = (await e.Server.CreateChannel(GetChannelName(afterVch.Name), ChannelType.Text).ConfigureAwait(false)); - await textChannel.AddPermissionsRule(e.Server.EveryoneRole, - new ChPermOverride(readMessages: PermValue.Deny, - sendMessages: PermValue.Deny)).ConfigureAwait(false); - } - await textChannel.AddPermissionsRule(e.After, - new ChPermOverride(readMessages: PermValue.Allow, - sendMessages: PermValue.Allow)).ConfigureAwait(false); - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - }; - } - - private string GetChannelName(string voiceName) => - channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice"; - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "cleanv+t") - .Alias(Module.Prefix + "cv+t") - .Description($"Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk. Needs Manage Roles and Manage Channels Permissions.** | `{Prefix}cleanv+t`") - .AddCheck(SimpleCheckers.CanManageRoles) - .AddCheck(SimpleCheckers.ManageChannels()) - .Do(async e => - { - if (!e.Server.CurrentUser.ServerPermissions.ManageChannels) - { - await e.Channel.SendMessage("`I have insufficient permission to do that.`"); - return; - } - - var allTxtChannels = e.Server.TextChannels.Where(c => c.Name.EndsWith("-voice")); - var validTxtChannelNames = e.Server.VoiceChannels.Select(c => GetChannelName(c.Name)); - - var invalidTxtChannels = allTxtChannels.Where(c => !validTxtChannelNames.Contains(c.Name)); - - foreach (var c in invalidTxtChannels) - { - try - { - await c.Delete(); - } - catch { } - await Task.Delay(500); - } - - await e.Channel.SendMessage("`Done.`"); - }); - - cgb.CreateCommand(Module.Prefix + "voice+text") - .Alias(Module.Prefix + "v+t") - .Description("Creates a text channel for each voice channel only users in that voice channel can see." + - $"If you are server owner, keep in mind you will see them all the time regardless. **Needs Manage Roles and Manage Channels Permissions.**| `{Prefix}voice+text`") - .AddCheck(SimpleCheckers.ManageChannels()) - .AddCheck(SimpleCheckers.CanManageRoles) - .Do(async e => - { - try - { - var config = SpecificConfigurations.Default.Of(e.Server.Id); - if (config.VoicePlusTextEnabled == true) - { - config.VoicePlusTextEnabled = false; - foreach (var textChannel in e.Server.TextChannels.Where(c => c.Name.EndsWith("-voice"))) - { - try - { - await textChannel.Delete().ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage( - ":anger: Error: Most likely i don't have permissions to do this.") - .ConfigureAwait(false); - return; - } - } - await e.Channel.SendMessage("Successfuly removed voice + text feature.").ConfigureAwait(false); - return; - } - config.VoicePlusTextEnabled = true; - await e.Channel.SendMessage("Successfuly enabled voice + text feature. " + - "**Make sure the bot has manage roles and manage channels permissions**") - .ConfigureAwait(false); - - } - catch (Exception ex) - { - await e.Channel.SendMessage(ex.ToString()).ConfigureAwait(false); - } - }); - } - } -} diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs deleted file mode 100644 index 1ae491ad..00000000 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs +++ /dev/null @@ -1,176 +0,0 @@ -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Text; -//using Manatee.Json.Serialization; - -namespace NadekoBot.Classes.ClashOfClans -{ - public enum DestroyStars - { - One, Two, Three - } - public enum WarState - { - Started, Ended, Created - } - [System.Serializable] - internal class Caller - { - public string CallUser { get; set; } - - public DateTime TimeAdded { get; set; } - - public bool BaseDestroyed { get; set; } - - public int Stars { get; set; } = 3; - - public Caller() { } - - public Caller(string callUser, DateTime timeAdded, bool baseDestroyed) - { - CallUser = callUser; - TimeAdded = timeAdded; - BaseDestroyed = baseDestroyed; - } - - public void ResetTime() - { - TimeAdded = DateTime.UtcNow; - } - - public void Destroy() - { - BaseDestroyed = true; - } - } - - internal class ClashWar - { - private static TimeSpan callExpire => new TimeSpan(2, 0, 0); - - public string EnemyClan { get; set; } - public int Size { get; set; } - - public Caller[] Bases { get; set; } - public WarState WarState { get; set; } = WarState.Created; - //public bool Started { get; set; } = false; - public DateTime StartedAt { get; set; } - //public bool Ended { get; private set; } = false; - - public ulong ServerId { get; set; } - public ulong ChannelId { get; set; } - - [JsonIgnore] - public Discord.Channel Channel { get; internal set; } - - /// - /// This init is purely for the deserialization - /// - public ClashWar() { } - - public ClashWar(string enemyClan, int size, ulong serverId, ulong channelId) - { - this.EnemyClan = enemyClan; - this.Size = size; - this.Bases = new Caller[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() - { - //Ended = true; - WarState = WarState.Ended; - } - - internal void Call(string u, int baseNumber) - { - if (baseNumber < 0 || baseNumber >= Bases.Length) - throw new ArgumentException("Invalid base number"); - if (Bases[baseNumber] != null) - throw new ArgumentException("That base is already claimed."); - for (var i = 0; i < Bases.Length; i++) - { - if (Bases[i]?.BaseDestroyed == false && Bases[i]?.CallUser == u) - throw new ArgumentException($"@{u} You already claimed base #{i + 1}. You can't claim a new one."); - } - - Bases[baseNumber] = new Caller(u.Trim(), DateTime.UtcNow, false); - } - - internal void Start() - { - if (WarState == WarState.Started) - throw new InvalidOperationException("War already started"); - //if (Started) - // throw new InvalidOperationException(); - //Started = true; - WarState = WarState.Started; - StartedAt = DateTime.UtcNow; - foreach (var b in Bases.Where(b => b != null)) - { - b.ResetTime(); - } - } - - internal int Uncall(string user) - { - user = user.Trim(); - for (var i = 0; i < Bases.Length; i++) - { - if (Bases[i]?.CallUser != user) continue; - Bases[i] = null; - return i; - } - throw new InvalidOperationException("You are not participating in that war."); - } - - public string ShortPrint() => - $"`{EnemyClan}` ({Size} v {Size})"; - - public override string ToString() - { - var sb = new StringBuilder(); - - sb.AppendLine($"🔰**WAR AGAINST `{EnemyClan}` ({Size} v {Size}) INFO:**"); - if (WarState == WarState.Created) - sb.AppendLine("`not started`"); - for (var i = 0; i < Bases.Length; i++) - { - if (Bases[i] == null) - { - sb.AppendLine($"`{i + 1}.` ❌*unclaimed*"); - } - else - { - if (Bases[i].BaseDestroyed) - { - sb.AppendLine($"`{i + 1}.` ✅ `{Bases[i].CallUser}` {new string('⭐', Bases[i].Stars)}"); - } - else - { - 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"); - } - } - - } - return sb.ToString(); - } - - internal int FinishClaim(string user, int stars = 3) - { - user = user.Trim(); - for (var i = 0; i < Bases.Length; i++) - { - if (Bases[i]?.BaseDestroyed != false || Bases[i]?.CallUser != user) continue; - Bases[i].BaseDestroyed = true; - Bases[i].Stars = stars; - return i; - } - throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base."); - } - } -} diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs b/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs deleted file mode 100644 index 07db4f98..00000000 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs +++ /dev/null @@ -1,421 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes.ClashOfClans; -using NadekoBot.Modules.Permissions.Classes; -using Newtonsoft.Json; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.ClashOfClans -{ - internal class ClashOfClansModule : DiscordModule - { - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.ClashOfClans; - - public static ConcurrentDictionary> ClashWars { get; set; } = new ConcurrentDictionary>(); - - 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>>(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>(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 newVal = new List(); - 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 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) - { - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - cgb.CreateCommand(Prefix + "createwar") - .Alias(Prefix + "cw") - .Description($"Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | `{Prefix}cw 15 The Enemy Clan`") - .Parameter("size") - .Parameter("enemy_clan", ParameterType.Unparsed) - .Do(async e => - { - if (!e.User.ServerPermissions.ManageChannels) - return; - var enemyClan = e.GetArg("enemy_clan"); - if (string.IsNullOrWhiteSpace(enemyClan)) - { - return; - } - int size; - if (!int.TryParse(e.GetArg("size"), out size) || size < 10 || size > 50 || size % 5 != 0) - { - await e.Channel.SendMessage("💢🔰 Not a Valid war size").ConfigureAwait(false); - return; - } - List wars; - if (!ClashWars.TryGetValue(e.Server.Id, out wars)) - { - wars = new List(); - if (!ClashWars.TryAdd(e.Server.Id, wars)) - return; - } - - - var cw = new ClashWar(enemyClan, size, e.Server.Id, e.Channel.Id); - //cw.Start(); - - wars.Add(cw); - await e.Channel.SendMessage($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false); - Save(); - //war with the index X started. - }); - - cgb.CreateCommand(Prefix + "startwar") - .Alias(Prefix + "sw") - .Description($"Starts a war with a given number. | `{Prefix}sw 15`") - .Parameter("number", ParameterType.Required) - .Do(async e => - { - var warsInfo = GetInfo(e); - if (warsInfo == null) - { - await e.Channel.SendMessage("💢🔰 **That war does not exist.**").ConfigureAwait(false); - return; - } - var war = warsInfo.Item1[warsInfo.Item2]; - try - { - war.Start(); - await e.Channel.SendMessage($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage($"🔰**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false); - } - Save(); - }); - - cgb.CreateCommand(Prefix + "listwar") - .Alias(Prefix + "lw") - .Description($"Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | `{Prefix}lw [war_number] or {Prefix}lw`") - .Parameter("number", ParameterType.Optional) - .Do(async e => - { - // if number is null, print all wars in a short way - if (string.IsNullOrWhiteSpace(e.GetArg("number"))) - { - //check if there are any wars - List wars = null; - ClashWars.TryGetValue(e.Server.Id, out wars); - if (wars == null || wars.Count == 0) - { - await e.Channel.SendMessage("🔰 **No active wars.**").ConfigureAwait(false); - return; - } - - var sb = new StringBuilder(); - sb.AppendLine("🔰 **LIST OF ACTIVE WARS**"); - sb.AppendLine("**-------------------------**"); - for (var i = 0; i < wars.Count; i++) - { - sb.AppendLine($"**#{i + 1}.** `Enemy:` **{wars[i].EnemyClan}**"); - sb.AppendLine($"\t\t`Size:` **{wars[i].Size} v {wars[i].Size}**"); - sb.AppendLine("**-------------------------**"); - } - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - return; - - } - //if number is not null, print the war needed - var warsInfo = GetInfo(e); - if (warsInfo == null) - { - await e.Channel.SendMessage("💢🔰 **That war does not exist.**").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage(warsInfo.Item1[warsInfo.Item2].ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "claim") - .Alias(Prefix + "call") - .Alias(Prefix + "c") - .Description($"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. | `{Prefix}call [war_number] [base_number] [optional_other_name]`") - .Parameter("number") - .Parameter("baseNumber") - .Parameter("other_name", ParameterType.Unparsed) - .Do(async e => - { - var warsInfo = GetInfo(e); - if (warsInfo == null || warsInfo.Item1.Count == 0) - { - await e.Channel.SendMessage("💢🔰 **That war does not exist.**").ConfigureAwait(false); - return; - } - int baseNum; - if (!int.TryParse(e.GetArg("baseNumber"), out baseNum)) - { - await e.Channel.SendMessage("💢🔰 **Invalid base number.**").ConfigureAwait(false); - return; - } - var usr = - string.IsNullOrWhiteSpace(e.GetArg("other_name")) ? - e.User.Name : - e.GetArg("other_name"); - try - { - var war = warsInfo.Item1[warsInfo.Item2]; - war.Call(usr, baseNum - 1); - await e.Channel.SendMessage($"🔰**{usr}** claimed a base #{baseNum} for a war against {war.ShortPrint()}").ConfigureAwait(false); - Save(); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢🔰 {ex.Message}").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "claimfinish") - .Alias(Prefix + "cf") - .Alias(Prefix + "cf3") - .Alias(Prefix + "claimfinish3") - .Description($"Finish your claim with 3 stars if you destroyed a base. Optional second argument finishes for someone else. | `{Prefix}cf [war_number] [optional_other_name]`") - .Parameter("number", ParameterType.Required) - .Parameter("other_name", ParameterType.Unparsed) - .Do(e => FinishClaim(e)); - - cgb.CreateCommand(Prefix + "claimfinish2") - .Alias(Prefix + "cf2") - .Description($"Finish your claim with 2 stars if you destroyed a base. Optional second argument finishes for someone else. | `{Prefix}cf [war_number] [optional_other_name]`") - .Parameter("number", ParameterType.Required) - .Parameter("other_name", ParameterType.Unparsed) - .Do(e => FinishClaim(e, 2)); - - cgb.CreateCommand(Prefix + "claimfinish1") - .Alias(Prefix + "cf1") - .Description($"Finish your claim with 1 stars if you destroyed a base. Optional second argument finishes for someone else. | `{Prefix}cf [war_number] [optional_other_name]`") - .Parameter("number", ParameterType.Required) - .Parameter("other_name", ParameterType.Unparsed) - .Do(e => FinishClaim(e, 1)); - - cgb.CreateCommand(Prefix + "unclaim") - .Alias(Prefix + "uncall") - .Alias(Prefix + "uc") - .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("other_name", ParameterType.Unparsed) - .Do(async e => - { - var warsInfo = GetInfo(e); - if (warsInfo == null || warsInfo.Item1.Count == 0) - { - await e.Channel.SendMessage("💢🔰 **That war does not exist.**").ConfigureAwait(false); - return; - } - var usr = - string.IsNullOrWhiteSpace(e.GetArg("other_name")) ? - e.User.Name : - e.GetArg("other_name"); - try - { - var war = warsInfo.Item1[warsInfo.Item2]; - var baseNumber = war.Uncall(usr); - await e.Channel.SendMessage($"🔰 @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false); - Save(); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢🔰 {ex.Message}").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "endwar") - .Alias(Prefix + "ew") - .Description($"Ends the war with a given index. | `{Prefix}ew [war_number]`") - .Parameter("number") - .Do(async e => - { - var warsInfo = GetInfo(e); - if (warsInfo == null) - { - await e.Channel.SendMessage("💢🔰 That war does not exist.").ConfigureAwait(false); - return; - } - 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; - warsInfo.Item1.RemoveAt(warsInfo.Item2); - Save(); - }); - }); - - } - #endregion - - - private async Task FinishClaim(CommandEventArgs e, int stars = 3) - { - var warInfo = GetInfo(e); - if (warInfo == null || warInfo.Item1.Count == 0) - { - await e.Channel.SendMessage("💢🔰 **That war does not exist.**").ConfigureAwait(false); - return; - } - var usr = - string.IsNullOrWhiteSpace(e.GetArg("other_name")) ? - e.User.Name : - e.GetArg("other_name"); - - var war = warInfo.Item1[warInfo.Item2]; - try - { - 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); - Save(); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢🔰 {ex.Message}").ConfigureAwait(false); - } - } - - private static Tuple, int> GetInfo(CommandEventArgs e) - { - //check if there are any wars - List wars = null; - ClashWars.TryGetValue(e.Server.Id, out wars); - if (wars == null || wars.Count == 0) - { - return null; - } - // get the number of the war - int num; - if (string.IsNullOrWhiteSpace(e.GetArg("number"))) - num = 0; - else if (!int.TryParse(e.GetArg("number"), out num) || num > wars.Count) - { - return null; - } - num -= 1; - //get the actual war - return new Tuple, int>(wars, num); - } - } -} diff --git a/NadekoBot/Modules/Conversations/Commands/RipCommand.cs b/NadekoBot/Modules/Conversations/Commands/RipCommand.cs deleted file mode 100644 index 9c4842bf..00000000 --- a/NadekoBot/Modules/Conversations/Commands/RipCommand.cs +++ /dev/null @@ -1,120 +0,0 @@ -using NadekoBot.Classes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Discord.Commands; -using System.Drawing; -using System.Drawing.Drawing2D; -using NadekoBot.Properties; -using System.IO; -using System.Drawing.Imaging; -using NadekoBot.Extensions; - -namespace NadekoBot.Modules.Conversations.Commands -{ - class RipCommand : DiscordCommand - { - public RipCommand(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand("rip") - .Description("Shows a grave image of someone with a start year | `@NadekoBot rip @Someone 2000`") - .Parameter("user", ParameterType.Required) - .Parameter("year", ParameterType.Optional) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("user"))) - return; - var usr = e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault(); - var text = ""; - Stream file; - if (usr == null) - { - text = e.GetArg("user"); - file = RipName(text, string.IsNullOrWhiteSpace(e.GetArg("year")) - ? null - : e.GetArg("year")); - } - else - { - var avatar = await GetAvatar(usr.AvatarUrl); - text = usr.Name; - file = RipUser(text, avatar, string.IsNullOrWhiteSpace(e.GetArg("year")) - ? null - : e.GetArg("year")); - } - await e.Channel.SendFile("ripzor_m8.png", - file); - }); - } - - - /// - /// Create a RIP image of the given name and avatar, with an optional year - /// - /// - /// - /// - /// - public Stream RipUser(string name, Image avatar, string year = null) - { - var bm = Resources.rip; - int width = 300; - var fontSize = width / name.Length -2; - if (fontSize > 20) fontSize = 20; - var g = Graphics.FromImage(bm); - Font nameFont = new Font("Comic Sans MS", fontSize, FontStyle.Bold, GraphicsUnit.Pixel); - SizeF nameSize = g.MeasureString(name, nameFont); - g.DrawString(name, new Font("Comic Sans MS", fontSize, FontStyle.Bold), Brushes.Black, (bm.Width /2 - 8) - (nameSize.Width /2), 243 - nameSize.Height); - g.DrawString((year ?? "?") + " - " + DateTime.Now.Year, new Font("Consolas", 12, FontStyle.Bold), Brushes.Black, 80, 240); - - g.DrawImage(avatar, 80, 135); - g.DrawImage((Image)Resources.rose_overlay, 0, 0); - g.Flush(); - g.Dispose(); - - return bm.ToStream(ImageFormat.Png); - } - - public Stream RipName(string name, string year = null) - { - var bm = Resources.rip; - int width = 190; - var offset = name.Length * 5; - var fontSize = width / name.Length; - if (fontSize > 20) fontSize = 20; - var g = Graphics.FromImage(bm); - Font nameFont = new Font("Comic Sans MS", fontSize, FontStyle.Bold, GraphicsUnit.Pixel); - SizeF nameSize = g.MeasureString(name, nameFont); - g.DrawString(name, nameFont, Brushes.Black, (bm.Width / 2) - (nameSize.Width / 2), 200); - g.DrawString((year ?? "?") + " - " + DateTime.Now.Year, new Font("Consolas", 12, FontStyle.Bold), Brushes.Black, 80, 235); - g.Flush(); - g.Dispose(); - - return bm.ToStream(ImageFormat.Png); - } - - public static async Task GetAvatar(string url) - { - var stream = await SearchHelper.GetResponseStreamAsync(url); - Bitmap bmp = new Bitmap(100, 100); - using (GraphicsPath gp = new GraphicsPath()) - { - gp.AddEllipse(0, 0, bmp.Width, bmp.Height); - using (Graphics gr = Graphics.FromImage(bmp)) - { - gr.SetClip(gp); - gr.DrawImage(Image.FromStream(stream), Point.Empty); - - } - } - return bmp; - - } - } -} diff --git a/NadekoBot/Modules/Conversations/Conversations.cs b/NadekoBot/Modules/Conversations/Conversations.cs deleted file mode 100644 index a3611d0e..00000000 --- a/NadekoBot/Modules/Conversations/Conversations.cs +++ /dev/null @@ -1,224 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.Modules; -using NadekoBot.DataModels; -using NadekoBot.Extensions; -using NadekoBot.Modules.Conversations.Commands; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Conversations -{ - internal class Conversations : DiscordModule - { - private const string firestr = "🔥 ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้ 🔥"; - public Conversations() - { - commands.Add(new RipCommand(this)); - } - - public override string Prefix { get; } = String.Format(NadekoBot.Config.CommandPrefixes.Conversations, NadekoBot.Creds.BotId); - - public override void Install(ModuleManager manager) - { - var rng = new Random(); - - manager.CreateCommands("", cgb => - { - cgb.AddCheck(PermissionChecker.Instance); - - cgb.CreateCommand("..") - .Description("Adds a new quote with the specified name (single word) and message (no limit). | `.. abc My message`") - .Parameter("keyword", ParameterType.Required) - .Parameter("text", ParameterType.Unparsed) - .Do(async e => - { - var text = e.GetArg("text"); - if (string.IsNullOrWhiteSpace(text)) - return; - await Task.Run(() => - Classes.DbHandler.Instance.Connection.Insert(new DataModels.UserQuote() - { - DateAdded = DateTime.Now, - Keyword = e.GetArg("keyword").ToLowerInvariant(), - Text = text, - UserName = e.User.Name, - })).ConfigureAwait(false); - - await e.Channel.SendMessage("`New quote added.`").ConfigureAwait(false); - }); - - cgb.CreateCommand("...") - .Description("Shows a random quote with a specified name. | `... abc`") - .Parameter("keyword", ParameterType.Required) - .Do(async e => - { - var keyword = e.GetArg("keyword")?.ToLowerInvariant(); - if (string.IsNullOrWhiteSpace(keyword)) - return; - - var quote = - Classes.DbHandler.Instance.GetRandom( - uqm => uqm.Keyword == keyword); - - if (quote != null) - await e.Channel.SendMessage($"📣 {quote.Text}").ConfigureAwait(false); - else - await e.Channel.SendMessage("💢`No quote found.`").ConfigureAwait(false); - }); - - cgb.CreateCommand("..qdel") - .Alias("..quotedelete") - .Description("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`") - .Parameter("quote", ParameterType.Required) - .Do(async e => - { - var text = e.GetArg("quote")?.Trim(); - if (string.IsNullOrWhiteSpace(text)) - return; - await Task.Run(() => - { - if (NadekoBot.IsOwner(e.User.Id)) - Classes.DbHandler.Instance.DeleteWhere(uq => uq.Keyword == text); - else - Classes.DbHandler.Instance.DeleteWhere(uq => uq.Keyword == text && uq.UserName == e.User.Name); - }).ConfigureAwait(false); - - await e.Channel.SendMessage("`Done.`").ConfigureAwait(false); - }); - }); - - manager.CreateCommands(NadekoBot.BotMention, cgb => - { - var client = manager.Client; - - cgb.AddCheck(PermissionChecker.Instance); - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand("die") - .Description("Works only for the owner. Shuts the bot down. | `@NadekoBot die`") - .Do(async e => - { - if (NadekoBot.IsOwner(e.User.Id)) - { - await e.Channel.SendMessage(e.User.Mention + ", Yes, my love.").ConfigureAwait(false); - await Task.Delay(5000).ConfigureAwait(false); - Environment.Exit(0); - } - else - await e.Channel.SendMessage(e.User.Mention + ", No.").ConfigureAwait(false); - }); - - var randServerSw = new Stopwatch(); - randServerSw.Start(); - - cgb.CreateCommand("do you love me") - .Description("Replies with positive answer only to the bot owner. | `@NadekoBot do you love me`") - .Do(async e => - { - if (NadekoBot.IsOwner(e.User.Id)) - await e.Channel.SendMessage(e.User.Mention + ", Of course I do, my Master.").ConfigureAwait(false); - else - await e.Channel.SendMessage(e.User.Mention + ", Don't be silly.").ConfigureAwait(false); - }); - - cgb.CreateCommand("how are you") - .Alias("how are you?") - .Description("Replies positive only if bot owner is online. | `@NadekoBot how are you`") - .Do(async e => - { - if (NadekoBot.IsOwner(e.User.Id)) - { - await e.Channel.SendMessage(e.User.Mention + " I am great as long as you are here.").ConfigureAwait(false); - return; - } - var kw = e.Server.GetUser(NadekoBot.Creds.OwnerIds[0]); - if (kw != null && kw.Status == UserStatus.Online) - { - await e.Channel.SendMessage(e.User.Mention + " I am great as long as " + kw.Mention + " is with me.").ConfigureAwait(false); - } - else - { - await e.Channel.SendMessage(e.User.Mention + " I am sad. My Master is not with me.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand("fire") - .Description("Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire. | `@NadekoBot fire [x]`") - .Parameter("times", ParameterType.Optional) - .Do(async e => - { - int count; - if (string.IsNullOrWhiteSpace(e.Args[0])) - count = 1; - else - int.TryParse(e.Args[0], out count); - if (count < 1 || count > 12) - { - await e.Channel.SendMessage("Number must be between 1 and 12").ConfigureAwait(false); - return; - } - - var str = new StringBuilder(); - for (var i = 0; i < count; i++) - { - str.Append(firestr); - } - await e.Channel.SendMessage(str.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand("dump") - .Description("Dumps all of the invites it can to dump.txt.** Owner Only.** | `@NadekoBot dump`") - .Do(async e => - { - if (!NadekoBot.IsOwner(e.User.Id)) return; - var i = 0; - var j = 0; - var invites = ""; - foreach (var s in client.Servers) - { - try - { - var invite = await s.CreateInvite(0).ConfigureAwait(false); - invites += invite.Url + "\n"; - i++; - } - catch - { - j++; - continue; - } - } - File.WriteAllText("dump.txt", invites); - await e.Channel.SendMessage($"Got invites for {i} servers and failed to get invites for {j} servers") - .ConfigureAwait(false); - }); - - cgb.CreateCommand("ab") - .Description("Try to get 'abalabahaha'| `@NadekoBot ab`") - .Do(async e => - { - string[] strings = { "ba", "la", "ha" }; - var construct = "@a"; - var cnt = rng.Next(4, 7); - while (cnt-- > 0) - { - construct += strings[rng.Next(0, strings.Length)]; - } - await e.Channel.SendMessage(construct).ConfigureAwait(false); - }); - - }); - } - - - - private static Func SayYes() - => async e => await e.Channel.SendMessage("Yes. :)").ConfigureAwait(false); - } -} diff --git a/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/NadekoBot/Modules/CustomReactions/CustomReactions.cs deleted file mode 100644 index b62b2dd7..00000000 --- a/NadekoBot/Modules/CustomReactions/CustomReactions.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace NadekoBot.Modules.CustomReactions -{ - internal class CustomReactionsModule : DiscordModule - { - public override string Prefix { get; } = ""; - - private Random rng = new Random(); - - private Dictionary> commandFuncs; - - public CustomReactionsModule() - { - commandFuncs = new Dictionary> - { - {new Regex(@"(?:%rng%|%rng:(\d{1,9})-(\d{1,9})%)"), (e,m) => { - int start, end; - if (m.Groups[1].Success) - { - start = int.Parse(m.Groups[1].Value); - end = int.Parse(m.Groups[2].Value); - return rng.Next(start, end).ToString(); - }else return rng.Next().ToString(); - } }, - {new Regex("%mention%"), (e,m) => NadekoBot.BotMention }, - {new Regex("%user%"), (e,m) => e.User.Mention }, - {new Regex("%target%"), (e,m) => e.GetArg("args")?.Trim() ?? "" }, - - }; - } - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - cgb.AddCheck(PermissionChecker.Instance); - - foreach (var command in NadekoBot.Config.CustomReactions) - { - var commandName = command.Key.Replace("%mention%", NadekoBot.BotMention); - - var c = cgb.CreateCommand(commandName); - if (commandName.Contains(NadekoBot.BotMention)) - c.Alias(commandName.Replace("<@", "<@!")); - c.Description($"Custom reaction. | `{command.Key}`") - .Parameter("args", ParameterType.Unparsed) - .Do(async e => - { - string str = command.Value[rng.Next(0, command.Value.Count())]; - commandFuncs.Keys.ForEach(key => str = key.Replace(str, m => commandFuncs[key](e, m))); - - - await e.Channel.SendMessage(str).ConfigureAwait(false); - }); - } - }); - } - } -} diff --git a/NadekoBot/Modules/DiscordCommand.cs b/NadekoBot/Modules/DiscordCommand.cs deleted file mode 100644 index 05f9a42a..00000000 --- a/NadekoBot/Modules/DiscordCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Discord.Commands; -using NadekoBot.Modules; - -namespace NadekoBot.Classes -{ - /// - /// Base DiscordCommand Class. - /// Inherit this class to create your own command. - /// - public abstract class DiscordCommand - { - - /// - /// Parent module - /// - protected DiscordModule Module { get; } - - /// - /// Parent module's prefix - /// - protected string Prefix => Module.Prefix; - - /// - /// Creates a new instance of discord command, - /// use ": base(module)" in the derived class' - /// constructor to make sure module is assigned - /// - /// Module this command resides in - protected DiscordCommand(DiscordModule module) - { - this.Module = module; - } - - /// - /// Initializes the CommandBuilder with values using CommandGroupBuilder - /// - internal abstract void Init(CommandGroupBuilder cgb); - } -} diff --git a/NadekoBot/Modules/DiscordModule.cs b/NadekoBot/Modules/DiscordModule.cs deleted file mode 100644 index 5e131233..00000000 --- a/NadekoBot/Modules/DiscordModule.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Discord.Modules; -using System.Collections.Generic; -using NadekoBot.Classes; - -namespace NadekoBot.Modules { - public abstract class DiscordModule : IModule { - protected readonly HashSet commands = new HashSet(); - - public abstract string Prefix { get; } - - public abstract void Install(ModuleManager manager); - } -} diff --git a/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs b/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs deleted file mode 100644 index a908128c..00000000 --- a/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs +++ /dev/null @@ -1,295 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Gambling.Commands -{ - class AnimalRacing : DiscordCommand - { - public static ConcurrentDictionary AnimalRaces = new ConcurrentDictionary(); - - public AnimalRacing(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Prefix + "race") - .Description($"Starts a new animal race. | `{Prefix}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. | `{Prefix}jr` or `{Prefix}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"); - return; - } - await ar.JoinRace(e.User, amount); - - }); - } - - public class AnimalRace - { - - private ConcurrentQueue animals = new ConcurrentQueue(NadekoBot.Config.RaceAnimals.Shuffle()); - - public bool Fail { get; internal set; } - - public List participants = new List(); - 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 {NadekoBot.Config.CommandPrefixes.Gambling}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 + " participants.`"); - } - 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 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`"; - } - - } - } - } -} diff --git a/NadekoBot/Modules/Gambling/DiceRollCommand.cs b/NadekoBot/Modules/Gambling/DiceRollCommand.cs deleted file mode 100644 index 326d85c4..00000000 --- a/NadekoBot/Modules/Gambling/DiceRollCommand.cs +++ /dev/null @@ -1,164 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Gambling -{ - internal class DiceRollCommand : DiscordCommand - { - - public DiceRollCommand(DiscordModule module) : base(module) { } - - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "roll") - .Description("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. | `{Prefix}roll` or `{Prefix}roll 7` or `{Prefix}roll 3d5`") - .Parameter("num", ParameterType.Optional) - .Do(RollFunc()); - - cgb.CreateCommand(Module.Prefix + "rolluo") - .Description("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. | `{Prefix}rolluo` or `{Prefix}rolluo 7` or `{Prefix}rolluo 3d5`") - .Parameter("num", ParameterType.Optional) - .Do(RollFunc(false)); - - cgb.CreateCommand(Module.Prefix + "nroll") - .Description($"Rolls in a given range. | `{Prefix}nroll 5` (rolls 0-5) or `{Prefix}nroll 5-15`") - .Parameter("range", ParameterType.Required) - .Do(NRollFunc()); - } - - private Image GetDice(int num) => num != 10 - ? Properties.Resources.ResourceManager.GetObject("_" + num) as Image - : new[] - { - (Properties.Resources.ResourceManager.GetObject("_" + 1) as Image), - (Properties.Resources.ResourceManager.GetObject("_" + 0) as Image), - }.Merge(); - - - Regex dndRegex = new Regex(@"(?\d+)d(?\d+)", RegexOptions.Compiled); - private Func RollFunc(bool ordered = true) - { - var r = new Random(); - return async e => - { - var arg = e.Args[0]?.Trim(); - if (string.IsNullOrWhiteSpace(arg)) - { - var gen = r.Next(0, 101); - - var num1 = gen / 10; - var num2 = gen % 10; - - var imageStream = new Image[2] { GetDice(num1), GetDice(num2) }.Merge().ToStream(ImageFormat.Png); - - await e.Channel.SendFile("dice.png", imageStream).ConfigureAwait(false); - return; - } - Match m; - if ((m = dndRegex.Match(arg)).Length != 0) - { - int n1; - int n2; - if (int.TryParse(m.Groups["n1"].ToString(), out n1) && - int.TryParse(m.Groups["n2"].ToString(), out n2) && - n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0) - { - var arr = new int[n1]; - for (int i = 0; i < n1; i++) - { - arr[i] = r.Next(1, n2 + 1); - } - var elemCnt = 0; - await e.Channel.SendMessage($"`Rolled {n1} {(n1 == 1 ? "die" : "dice")} 1-{n2}.`\n`Result:` " + string.Join(", ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false); - } - return; - } - try - { - var num = int.Parse(e.Args[0]); - if (num < 1) num = 1; - if (num > 30) - { - await e.Channel.SendMessage("You can roll up to 30 dice at a time.").ConfigureAwait(false); - num = 30; - } - var dices = new List(num); - var values = new List(num); - for (var i = 0; i < num; i++) - { - var randomNumber = r.Next(1, 7); - var toInsert = dices.Count; - if (ordered) - { - if (randomNumber == 6 || dices.Count == 0) - toInsert = 0; - else if (randomNumber != 1) - for (var j = 0; j < dices.Count; j++) - { - if (values[j] < randomNumber) - { - toInsert = j; - break; - } - } - } - else { - toInsert = dices.Count; - } - dices.Insert(toInsert, GetDice(randomNumber)); - values.Insert(toInsert, randomNumber); - } - - var bitmap = dices.Merge(); - await e.Channel.SendMessage(values.Count + " Dice rolled. Total: **" + values.Sum() + "** Average: **" + (values.Sum() / (1.0f * values.Count)).ToString("N2") + "**").ConfigureAwait(false); - await e.Channel.SendFile("dice.png", bitmap.ToStream(ImageFormat.Png)).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Please enter a number of dice to roll.").ConfigureAwait(false); - } - }; - } - - - private Func NRollFunc() => - async e => - { - try - { - int rolled; - if (e.GetArg("range").Contains("-")) - { - var arr = e.GetArg("range").Split('-') - .Take(2) - .Select(int.Parse) - .ToArray(); - if (arr[0] > arr[1]) - throw new ArgumentException("First argument should be bigger than the second one."); - rolled = new Random().Next(arr[0], arr[1] + 1); - } - else - { - rolled = new Random().Next(0, int.Parse(e.GetArg("range")) + 1); - } - - await e.Channel.SendMessage($"{e.User.Mention} rolled **{rolled}**.").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($":anger: {ex.Message}").ConfigureAwait(false); - } - }; - } -} diff --git a/NadekoBot/Modules/Gambling/DrawCommand.cs b/NadekoBot/Modules/Gambling/DrawCommand.cs deleted file mode 100644 index dbd023e1..00000000 --- a/NadekoBot/Modules/Gambling/DrawCommand.cs +++ /dev/null @@ -1,91 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Gambling.Helpers; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Drawing; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Gambling -{ - internal class DrawCommand : DiscordCommand - { - public DrawCommand(DiscordModule module) : base(module) { } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "draw") - .Description($"Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | `{Prefix}draw [x]`") - .Parameter("count", ParameterType.Optional) - .Do(DrawCardFunc()); - - cgb.CreateCommand(Module.Prefix + "shuffle") - .Alias(Module.Prefix + "sh") - .Description($"Reshuffles all cards back into the deck.|`{Prefix}shuffle`") - .Do(ReshuffleTask()); - } - - private static readonly ConcurrentDictionary AllDecks = new ConcurrentDictionary(); - - private static Func ReshuffleTask() - { - return async e => - { - AllDecks.AddOrUpdate(e.Server, - (s) => new Cards(), - (s, c) => - { - c.Restart(); - return c; - }); - - await e.Channel.SendMessage("Deck reshuffled.").ConfigureAwait(false); - }; - } - - private Func DrawCardFunc() => async (e) => - { - var cards = AllDecks.GetOrAdd(e.Server, (s) => new Cards()); - - try - { - var num = 1; - var isParsed = int.TryParse(e.GetArg("count"), out num); - if (!isParsed || num < 2) - { - var c = cards.DrawACard(); - await e.Channel.SendFile(c.Name + ".jpg", (Properties.Resources.ResourceManager.GetObject(c.Name) as Image).ToStream()).ConfigureAwait(false); - return; - } - if (num > 5) - num = 5; - - var images = new List(); - var cardObjects = new List(); - for (var i = 0; i < num; i++) - { - if (cards.CardPool.Count == 0 && i != 0) - { - await e.Channel.SendMessage("No more cards in a deck.").ConfigureAwait(false); - break; - } - var currentCard = cards.DrawACard(); - cardObjects.Add(currentCard); - images.Add(Properties.Resources.ResourceManager.GetObject(currentCard.Name) as Image); - } - var bitmap = images.Merge(); - await e.Channel.SendFile(images.Count + " cards.jpg", bitmap.ToStream()).ConfigureAwait(false); - if (cardObjects.Count == 5) - { - await e.Channel.SendMessage($"{e.User.Mention} `{Cards.GetHandValue(cardObjects)}`").ConfigureAwait(false); - } - } - catch (Exception ex) - { - Console.WriteLine("Error drawing (a) card(s) " + ex.ToString()); - } - }; - } -} diff --git a/NadekoBot/Modules/Gambling/FlipCoinCommand.cs b/NadekoBot/Modules/Gambling/FlipCoinCommand.cs deleted file mode 100644 index d0b79919..00000000 --- a/NadekoBot/Modules/Gambling/FlipCoinCommand.cs +++ /dev/null @@ -1,112 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using System; -using System.Drawing; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Gambling -{ - internal class FlipCoinCommand : DiscordCommand - { - - public FlipCoinCommand(DiscordModule module) : base(module) { } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "flip") - .Description($"Flips coin(s) - heads or tails, and shows an image. | `{Prefix}flip` or `{Prefix}flip 3`") - .Parameter("count", ParameterType.Optional) - .Do(FlipCoinFunc()); - - cgb.CreateCommand(Module.Prefix + "betflip") - .Alias(Prefix+"bf") - .Description($"Bet to guess will the result be heads or tails. Guessing award you double flowers you've bet. | `{Prefix}bf 5 heads` or `{Prefix}bf 3 t`") - .Parameter("amount", ParameterType.Required) - .Parameter("guess", ParameterType.Required) - .Do(BetFlipCoinFunc()); - } - - - - private readonly Random rng = new Random(); - public Func BetFlipCoinFunc() => async e => - { - - var amountstr = e.GetArg("amount").Trim(); - - var guessStr = e.GetArg("guess").Trim().ToUpperInvariant(); - if (guessStr != "H" && guessStr != "T" && guessStr != "HEADS" && guessStr != "TAILS") - return; - - int amount; - if (!int.TryParse(amountstr, out amount) || amount < 1) - return; - - 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; - } - - await FlowersHandler.RemoveFlowers(e.User, "Betflip Gamble", (int)amount, true).ConfigureAwait(false); - //heads = true - //tails = false - - var guess = guessStr == "HEADS" || guessStr == "H"; - bool result = false; - if (rng.Next(0, 2) == 1) { - await e.Channel.SendFile("heads.png", Properties.Resources.heads.ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false); - result = true; - } - else { - await e.Channel.SendFile("tails.png", Properties.Resources.tails.ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false); - } - - string str; - if (guess == result) - { - str = $"{e.User.Mention}`You guessed it!` You won {amount * 2}{NadekoBot.Config.CurrencySign}"; - await FlowersHandler.AddFlowersAsync(e.User, "Betflip Gamble", amount * 2, true).ConfigureAwait(false); - - } - else - str = $"{e.User.Mention}`More luck next time.`"; - - await e.Channel.SendMessage(str).ConfigureAwait(false); - }; - - public Func FlipCoinFunc() => async e => - { - - if (e.GetArg("count") == "") - { - if (rng.Next(0, 2) == 1) - await e.Channel.SendFile("heads.png", Properties.Resources.heads.ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false); - else - await e.Channel.SendFile("tails.png", Properties.Resources.tails.ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false); - } - else - { - int result; - if (int.TryParse(e.GetArg("count"), out result)) - { - if (result > 10) - result = 10; - var imgs = new Image[result]; - for (var i = 0; i < result; i++) - { - imgs[i] = rng.Next(0, 2) == 0 ? - Properties.Resources.tails : - Properties.Resources.heads; - } - await e.Channel.SendFile($"{result} coins.png", imgs.Merge().ToStream(System.Drawing.Imaging.ImageFormat.Png)).ConfigureAwait(false); - return; - } - await e.Channel.SendMessage("Invalid number").ConfigureAwait(false); - } - }; - } -} diff --git a/NadekoBot/Modules/Gambling/GamblingModule.cs b/NadekoBot/Modules/Gambling/GamblingModule.cs deleted file mode 100644 index d7ced224..00000000 --- a/NadekoBot/Modules/Gambling/GamblingModule.cs +++ /dev/null @@ -1,214 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.DataModels; -using NadekoBot.Extensions; -using NadekoBot.Modules.Gambling.Commands; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Linq; -using System.Text; - -namespace NadekoBot.Modules.Gambling -{ - internal class GamblingModule : DiscordModule - { - public GamblingModule() - { - commands.Add(new DrawCommand(this)); - commands.Add(new FlipCoinCommand(this)); - commands.Add(new DiceRollCommand(this)); - commands.Add(new AnimalRacing(this)); - } - - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Gambling; - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - cgb.AddCheck(PermissionChecker.Instance); - - commands.ForEach(com => com.Init(cgb)); - - cgb.CreateCommand(Prefix + "raffle") - .Description($"Prints a name and ID of a random user from the online list from the (optional) role. | `{Prefix}raffle` or `{Prefix}raffle RoleName`") - .Parameter("role", ParameterType.Optional) - .Do(async e => - { - var arg = string.IsNullOrWhiteSpace(e.GetArg("role")) ? "@everyone" : e.GetArg("role"); - var role = e.Server.FindRoles(arg).FirstOrDefault(); - if (role == null) - { - await e.Channel.SendMessage("💢 Role not found.").ConfigureAwait(false); - return; - } - var members = role.Members.Where(u => u.Status == UserStatus.Online); // only online - var membersArray = members as User[] ?? members.ToArray(); - var usr = membersArray[new Random().Next(0, membersArray.Length)]; - await e.Channel.SendMessage($"**Raffled user:** {usr.Name} (id: {usr.Id})").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "$$") - .Description(string.Format("Check how much {0}s a person has. (Defaults to yourself) |`{1}$$` or `{1}$$ @Someone`", - NadekoBot.Config.CurrencyName, Prefix)) - .Parameter("all", ParameterType.Unparsed) - .Do(async e => - { - var usr = e.Message.MentionedUsers.FirstOrDefault() ?? e.User; - var pts = GetUserFlowers(usr.Id); - var str = $"{usr.Name} has {pts} {NadekoBot.Config.CurrencySign}"; - await e.Channel.SendMessage(str).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "give") - .Description(string.Format("Give someone a certain amount of {0}s", NadekoBot.Config.CurrencyName)+ $"|`{Prefix}give 1 \"@SomeGuy\"`") - .Parameter("amount", ParameterType.Required) - .Parameter("receiver", ParameterType.Unparsed) - .Do(async e => - { - var amountStr = e.GetArg("amount")?.Trim(); - long amount; - if (!long.TryParse(amountStr, out amount) || amount <= 0) - return; - - var mentionedUser = e.Message.MentionedUsers.FirstOrDefault(u => - u.Id != NadekoBot.Client.CurrentUser.Id && - u.Id != e.User.Id); - if (mentionedUser == null) - return; - - var userFlowers = 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; - } - - await FlowersHandler.RemoveFlowers(e.User, "Gift", (int)amount, true).ConfigureAwait(false); - await FlowersHandler.AddFlowersAsync(mentionedUser, "Gift", (int)amount).ConfigureAwait(false); - - await e.Channel.SendMessage($"{e.User.Mention} successfully sent {amount} {NadekoBot.Config.CurrencyName}s to {mentionedUser.Mention}!").ConfigureAwait(false); - - }); - - cgb.CreateCommand(Prefix + "award") - .Description($"Gives someone a certain amount of flowers. **Bot Owner Only!** | `{Prefix}award 100 @person`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Parameter("amount", ParameterType.Required) - .Parameter("receiver", ParameterType.Unparsed) - .Do(async e => - { - var amountStr = e.GetArg("amount")?.Trim(); - long amount; - if (!long.TryParse(amountStr, out amount) || amount < 0) - return; - - var mentionedUser = e.Message.MentionedUsers.FirstOrDefault(u => - u.Id != NadekoBot.Client.CurrentUser.Id); - if (mentionedUser == null) - return; - - await FlowersHandler.AddFlowersAsync(mentionedUser, $"Awarded by bot owner. ({e.User.Name}/{e.User.Id})", (int)amount).ConfigureAwait(false); - - await e.Channel.SendMessage($"{e.User.Mention} successfully awarded {amount} {NadekoBot.Config.CurrencyName}s to {mentionedUser.Mention}!").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "take") - .Description($"Takes a certain amount of flowers from someone. **Bot Owner Only!** | `{Prefix}take 1 \"@someguy\"`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Parameter("amount", ParameterType.Required) - .Parameter("rektperson", ParameterType.Unparsed) - .Do(async e => - { - var amountStr = e.GetArg("amount")?.Trim(); - long amount; - if (!long.TryParse(amountStr, out amount) || amount < 0) - return; - - var mentionedUser = e.Message.MentionedUsers.FirstOrDefault(u => - u.Id != NadekoBot.Client.CurrentUser.Id); - if (mentionedUser == null) - return; - - await FlowersHandler.RemoveFlowers(mentionedUser, $"Taken by bot owner.({e.User.Name}/{e.User.Id})", (int)amount).ConfigureAwait(false); - - await e.Channel.SendMessage($"{e.User.Mention} successfully took {amount} {NadekoBot.Config.CurrencyName}s from {mentionedUser.Mention}!").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "betroll") - .Alias(Prefix + "br") - .Description($"Bets a certain amount of {NadekoBot.Config.CurrencyName}s and rolls a dice. Rolling over 66 yields x2 flowers, over 90 - x3 and 100 x10. | `{Prefix}br 5`") - .Parameter("amount",ParameterType.Required) - .Do(async e => - { - var amountstr = e.GetArg("amount").Trim(); - int amount; - - if (!int.TryParse(amountstr, out amount) || amount < 1) - return; - - var userFlowers = 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; - } - - await FlowersHandler.RemoveFlowers(e.User, "Betroll Gamble", (int)amount, true).ConfigureAwait(false); - - var rng = new Random().Next(0, 101); - var str = $"{e.User.Mention} `You rolled {rng}.` "; - if (rng < 67) - { - str += "Better luck next time."; - } - else if (rng < 90) - { - str += $"Congratulations! You won {amount * 2}{NadekoBot.Config.CurrencySign} for rolling above 66"; - await FlowersHandler.AddFlowersAsync(e.User, "Betroll Gamble", amount * 2, true).ConfigureAwait(false); - } - else if (rng < 100) - { - str += $"Congratulations! You won {amount * 3}{NadekoBot.Config.CurrencySign} for rolling above 90."; - await FlowersHandler.AddFlowersAsync(e.User, "Betroll Gamble", amount * 3, true).ConfigureAwait(false); - } - else { - str += $"👑 Congratulations! You won {amount * 10}{NadekoBot.Config.CurrencySign} for rolling **100**. 👑"; - await FlowersHandler.AddFlowersAsync(e.User, "Betroll Gamble", amount * 10, true).ConfigureAwait(false); - } - - await e.Channel.SendMessage(str).ConfigureAwait(false); - - }); - - cgb.CreateCommand(Prefix + "leaderboard") - .Alias(Prefix + "lb") - .Description($"Displays bot currency leaderboard | `{Prefix}lb`") - .Do(async e => - { - var richestTemp = DbHandler.Instance.GetTopRichest(); - var richest = richestTemp as CurrencyState[] ?? richestTemp.ToArray(); - if (richest.Length == 0) - return; - await e.Channel.SendMessage( - richest.Aggregate(new StringBuilder( - $@"```xl -┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓ -┃ Id ┃ $$$ ┃ -"), - (cur, cs) => cur.AppendLine( - $@"┣━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━┫ -┃{(e.Server.Users.Where(u => u.Id == (ulong)cs.UserId).FirstOrDefault()?.Name.TrimTo(18, true) ?? cs.UserId.ToString()),-20} ┃ {cs.Value,5} ┃") - ).ToString() + "┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━┛```").ConfigureAwait(false); - }); - }); - } - - public static long GetUserFlowers(ulong userId) => - Classes.DbHandler.Instance.GetStateByUserId((long)userId)?.Value ?? 0; - } -} diff --git a/NadekoBot/Modules/Games/Commands/BetrayGame.cs b/NadekoBot/Modules/Games/Commands/BetrayGame.cs deleted file mode 100644 index 74b98bf6..00000000 --- a/NadekoBot/Modules/Games/Commands/BetrayGame.cs +++ /dev/null @@ -1,129 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Games.Commands -{ - class BetrayGame : DiscordCommand - { - public BetrayGame(DiscordModule module) : base(module) { } - - private enum Answers - { - Cooperate, - Betray - } - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "betray") - .Description("BETRAY GAME. Betray nadeko next turn." + - "If Nadeko cooperates - you get extra points, nadeko loses a LOT." + - $"If Nadeko betrays - you both lose some points. | `{Prefix}betray`") - .Do(async e => - { - await ReceiveAnswer(e, Answers.Betray).ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "cooperate") - .Description("BETRAY GAME. Cooperate with nadeko next turn." + - "If Nadeko cooperates - you both get bonus points." + - $"If Nadeko betrays - you lose A LOT, nadeko gets extra. | `{Prefix}cooperater`") - .Do(async e => - { - - await ReceiveAnswer(e, Answers.Cooperate).ConfigureAwait(false); - }); - } - - private int userPoints = 0; - - private int UserPoints { - get { return userPoints; } - set { - if (value < 0) - userPoints = 0; - userPoints = value; - } - } - private int nadekoPoints = 0; - private int NadekoPoints { - get { return nadekoPoints; } - set { - if (value < 0) - nadekoPoints = 0; - nadekoPoints = value; - } - } - - private int round = 0; - private Answers NextAnswer = Answers.Cooperate; - private async Task ReceiveAnswer(CommandEventArgs e, Answers userAnswer) - { - var response = userAnswer == Answers.Betray - ? ":no_entry: `You betrayed nadeko` - you monster." - : ":ok: `You cooperated with nadeko.` "; - var currentAnswer = NextAnswer; - var nadekoResponse = currentAnswer == Answers.Betray - ? ":no_entry: `aww Nadeko betrayed you` - she is so cute" - : ":ok: `Nadeko cooperated.`"; - NextAnswer = userAnswer; - if (userAnswer == Answers.Betray && currentAnswer == Answers.Betray) - { - NadekoPoints--; - UserPoints--; - } - else if (userAnswer == Answers.Cooperate && currentAnswer == Answers.Cooperate) - { - NadekoPoints += 2; - UserPoints += 2; - } - else if (userAnswer == Answers.Betray && currentAnswer == Answers.Cooperate) - { - NadekoPoints -= 3; - UserPoints += 3; - } - else if (userAnswer == Answers.Cooperate && currentAnswer == Answers.Betray) - { - NadekoPoints += 3; - UserPoints -= 3; - } - - await e.Channel.SendMessage($"**ROUND {++round}**\n" + - $"{response}\n" + - $"{nadekoResponse}\n" + - $"--------------------------------\n" + - $"Nadeko has {NadekoPoints} points." + - $"You have {UserPoints} points." + - $"--------------------------------\n") - .ConfigureAwait(false); - if (round < 10) return; - if (nadekoPoints == userPoints) - await e.Channel.SendMessage("Its a draw").ConfigureAwait(false); - else if (nadekoPoints > userPoints) - await e.Channel.SendMessage("Nadeko won.").ConfigureAwait(false); - else - await e.Channel.SendMessage("You won.").ConfigureAwait(false); - nadekoPoints = 0; - userPoints = 0; - round = 0; - } - } - - public class BetraySetting - { - private string Story = $"{0} have robbed a bank and got captured by a police." + - $"Investigators gave you a choice:\n" + - $"You can either >COOPERATE with your friends and " + - $"not tell them who's idea it was, OR you can >BETRAY your" + - $"friends. Depending on their answers your penalty will vary."; - - public int DoubleCoop = 1; - public int DoubleBetray = -1; - public int BetrayCoop_Betrayer = 3; - public int BetrayCoop_Cooperater = -3; - - public string GetStory(IEnumerable names) => String.Format(Story, string.Join(", ", names)); - } -} diff --git a/NadekoBot/Modules/Games/Commands/Bomberman.cs b/NadekoBot/Modules/Games/Commands/Bomberman.cs deleted file mode 100644 index 4987dd66..00000000 --- a/NadekoBot/Modules/Games/Commands/Bomberman.cs +++ /dev/null @@ -1,125 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using System.Text; -using System.Timers; -using static NadekoBot.Modules.Games.Commands.Bomberman; - -namespace NadekoBot.Modules.Games.Commands -{ - class Bomberman : DiscordCommand - { - public Field[,] board = new Field[15, 15]; - - public BombermanPlayer[] players = new BombermanPlayer[4]; - - public Channel gameChannel = null; - - public Message godMsg = null; - - public int curI = 5; - public int curJ = 5; - - - public Bomberman(DiscordModule module) : base(module) - { - for (int i = 0; i < 15; i++) - { - for (int j = 0; j < 15; j++) - { - board[i, j] = new Field(); - } - } - NadekoBot.Client.MessageReceived += (s, e) => - { - if (e.Channel != gameChannel || - e.User.Id == NadekoBot.Client.CurrentUser.Id) - return; - - if (e.Message.Text == "w") - { - board[curI - 1, curJ] = board[curI--, curJ]; - board[curI + 1, curJ].player = null; - } - else if (e.Message.Text == "s") - { - board[curI + 1, curJ] = board[curI++, curJ]; - board[curI - 1, curJ].player = null; - } - else if (e.Message.Text == "a") - { - board[curI, curJ - 1] = board[curI, curJ--]; - board[curI, curJ + 1].player = null; - } - else if (e.Message.Text == "d") - { - board[curI, curJ + 1] = board[curI, curJ++]; - board[curI, curJ - 1].player = null; - } - - e.Message.Delete(); - }; - - var t = new Timer(); - t.Elapsed += async (s, e) => - { - if (gameChannel == null) - return; - - var boardStr = new StringBuilder(); - - for (int i = 0; i < 15; i++) - { - for (int j = 0; j < 15; j++) - { - boardStr.Append(board[i, j].ToString()); - } - boardStr.AppendLine(); - } - if (godMsg.Id != 0) - await godMsg.Edit(boardStr.ToString()).ConfigureAwait(false); - - }; - t.Interval = 1000; - t.Start(); - - } - - internal override void Init(CommandGroupBuilder cgb) - { - //cgb.CreateCommand(Module.Prefix + "bomb") - // .Description("Bomberman start") - // .Do(async e => - // { - // if (gameChannel != null) - // return; - // godMsg = await e.Channel.SendMessage("GAME START IN 1 SECOND....").ConfigureAwait(false); - // gameChannel = e.Channel; - // players[0] = new BombermanPlayer - // { - // User = e.User, - // }; - - // board[5, 5].player = players[0]; - // }); - } - - public class BombermanPlayer - { - public User User = null; - public string Icon = "👳"; - - internal void MoveLeft() - { - - } - } - } - - internal struct Field - { - public BombermanPlayer player; - - public override string ToString() => player?.Icon ?? "⬜"; - } -} diff --git a/NadekoBot/Modules/Games/Commands/PlantPick.cs b/NadekoBot/Modules/Games/Commands/PlantPick.cs deleted file mode 100644 index 2752ad1f..00000000 --- a/NadekoBot/Modules/Games/Commands/PlantPick.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Games.Commands -{ - /// - /// Flower picking/planting idea is given to me by its - /// inceptor Violent Crumble from Game Developers League discord server - /// (he has !cookie and !nom) Thanks a lot Violent! - /// Check out GDL (its a growing gamedev community): - /// https://discord.gg/0TYNJfCU4De7YIk8 - /// - class PlantPick : DiscordCommand - { - - private Random rng; - public PlantPick(DiscordModule module) : base(module) - { - NadekoBot.Client.MessageReceived += PotentialFlowerGeneration; - rng = new Random(); - } - - private static readonly ConcurrentDictionary plantpickCooldowns = new ConcurrentDictionary(); - - private async void PotentialFlowerGeneration(object sender, Discord.MessageEventArgs e) - { - try - { - if (e.Server == null || e.Channel.IsPrivate || e.Message.IsAuthor) - return; - var config = Classes.SpecificConfigurations.Default.Of(e.Server.Id); - var now = DateTime.Now; - int cd; - DateTime lastSpawned; - if (config.GenerateCurrencyChannels.TryGetValue(e.Channel.Id, out cd)) - if (!plantpickCooldowns.TryGetValue(e.Channel.Id, out lastSpawned) || (lastSpawned + new TimeSpan(0, cd, 0)) < now) - { - var rnd = Math.Abs(rng.Next(0,101)); - if (rnd == 0) - { - var msgs = new[] { await e.Channel.SendFile(GetRandomCurrencyImagePath()), await e.Channel.SendMessage($"❗ A random {NadekoBot.Config.CurrencyName} appeared! Pick it up by typing `>pick`") }; - plantedFlowerChannels.AddOrUpdate(e.Channel.Id, msgs, (u, m) => { m.ForEach(async msgToDelete => { try { await msgToDelete.Delete(); } catch { } }); return msgs; }); - plantpickCooldowns.AddOrUpdate(e.Channel.Id, now, (i, d) => now); - } - } - } - catch { } - } - //channelid/messageid pair - ConcurrentDictionary> plantedFlowerChannels = new ConcurrentDictionary>(); - - private SemaphoreSlim locker = new SemaphoreSlim(1,1); - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "pick") - .Description($"Picks a flower planted in this channel. | `{Prefix}pick`") - .Do(async e => - { - IEnumerable msgs; - - await e.Message.Delete().ConfigureAwait(false); - if (!plantedFlowerChannels.TryRemove(e.Channel.Id, out msgs)) - return; - - foreach(var msgToDelete in msgs) - await msgToDelete.Delete().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); - ThreadPool.QueueUserWorkItem(async (state) => - { - try - { - await Task.Delay(10000).ConfigureAwait(false); - await msg.Delete().ConfigureAwait(false); - } - catch { } - }); - }); - - 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) | `{Prefix}plant`") - .Do(async e => - { - await locker.WaitAsync().ConfigureAwait(false); - try - { - if (plantedFlowerChannels.ContainsKey(e.Channel.Id)) - { - await e.Channel.SendMessage($"There is already a {NadekoBot.Config.CurrencyName} in this channel.").ConfigureAwait(false); - return; - } - var removed = await FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1, true).ConfigureAwait(false); - if (!removed) - { - await e.Channel.SendMessage($"You don't have any {NadekoBot.Config.CurrencyName}s.").ConfigureAwait(false); - return; - } - - var file = GetRandomCurrencyImagePath(); - Message msg; - if (file == null) - msg = await e.Channel.SendMessage(NadekoBot.Config.CurrencySign).ConfigureAwait(false); - else - msg = await e.Channel.SendFile(file).ConfigureAwait(false); - var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.Config.CurrencyName[0]); - 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 }); - } - finally { locker.Release(); } - }); - - cgb.CreateCommand(Prefix + "gencurrency") - .Alias(Prefix + "gc") - .Description($"Toggles currency generation on this channel. Every posted message will have 2% chance to spawn a {NadekoBot.Config.CurrencyName}. Optional parameter cooldown time in minutes, 5 minutes by default. Requires Manage Messages permission. | `{Prefix}gc` or `{Prefix}gc 60`") - .AddCheck(SimpleCheckers.ManageMessages()) - .Parameter("cd", ParameterType.Unparsed) - .Do(async e => - { - var cdStr = e.GetArg("cd"); - int cd = 2; - if (!int.TryParse(cdStr, out cd) || cd < 0) - { - cd = 2; - } - var config = SpecificConfigurations.Default.Of(e.Server.Id); - int throwaway; - if (config.GenerateCurrencyChannels.TryRemove(e.Channel.Id, out throwaway)) - { - await e.Channel.SendMessage("`Currency generation disabled on this channel.`").ConfigureAwait(false); - } - else - { - if (config.GenerateCurrencyChannels.TryAdd(e.Channel.Id, cd)) - await e.Channel.SendMessage($"`Currency generation enabled on this channel. Cooldown is {cd} minutes.`").ConfigureAwait(false); - } - }); - } - - private string GetRandomCurrencyImagePath() => - Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); - - int GetRandomNumber() - { - using (RNGCryptoServiceProvider rg = new RNGCryptoServiceProvider()) - { - byte[] rno = new byte[4]; - rg.GetBytes(rno); - int randomvalue = BitConverter.ToInt32(rno, 0); - return randomvalue; - } - } - } -} diff --git a/NadekoBot/Modules/Games/Commands/PollCommand.cs b/NadekoBot/Modules/Games/Commands/PollCommand.cs deleted file mode 100644 index b7082803..00000000 --- a/NadekoBot/Modules/Games/Commands/PollCommand.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Games.Commands -{ - internal class PollCommand : DiscordCommand - { - - public static ConcurrentDictionary ActivePolls = new ConcurrentDictionary(); - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "poll") - .Description($"Creates a poll, only person who has manage server permission can do it. **Needs Manage Server Permissions**| `{Prefix}poll Question?;Answer1;Answ 2;A_3`") - .Parameter("allargs", ParameterType.Unparsed) - .Do(async e => - { - await Task.Run(async () => - { - if (!e.User.ServerPermissions.ManageChannels) - return; - if (ActivePolls.ContainsKey(e.Server)) - return; - var arg = e.GetArg("allargs"); - if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";")) - return; - var data = arg.Split(';'); - if (data.Length < 3) - return; - - var poll = new Poll(e, data[0], data.Skip(1)); - if (PollCommand.ActivePolls.TryAdd(e.Server, poll)) - { - await poll.StartPoll().ConfigureAwait(false); - } - }).ConfigureAwait(false); - }); - cgb.CreateCommand(Module.Prefix + "pollend") - .Description($"Stops active poll on this server and prints the results in this channel. | `{Prefix}pollend`") - .Do(async e => - { - if (!e.User.ServerPermissions.ManageChannels) - return; - if (!ActivePolls.ContainsKey(e.Server)) - return; - await ActivePolls[e.Server].StopPoll(e.Channel).ConfigureAwait(false); - }); - } - - public PollCommand(DiscordModule module) : base(module) { } - } - - internal class Poll - { - private readonly CommandEventArgs e; - private readonly string[] answers; - private ConcurrentDictionary participants = new ConcurrentDictionary(); - private readonly string question; - private DateTime started; - private CancellationTokenSource pollCancellationSource = new CancellationTokenSource(); - - public Poll(CommandEventArgs e, string question, IEnumerable enumerable) - { - this.e = e; - this.question = question; - this.answers = enumerable as string[] ?? enumerable.ToArray(); - } - - public async Task StartPoll() - { - started = DateTime.Now; - NadekoBot.Client.MessageReceived += Vote; - var msgToSend = - $"📃**{e.User.Name}** from **{e.Server.Name}** server has created a poll which requires your attention:\n\n" + - $"**{question}**\n"; - var num = 1; - msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); - msgToSend += "\n**Private Message me with the corresponding number of the answer.**"; - await e.Channel.SendMessage(msgToSend).ConfigureAwait(false); - } - - public async Task StopPoll(Channel ch) - { - NadekoBot.Client.MessageReceived -= Vote; - Poll throwaway; - PollCommand.ActivePolls.TryRemove(e.Server, out throwaway); - try - { - var results = participants.GroupBy(kvp => kvp.Value) - .ToDictionary(x => x.Key, x => x.Sum(kvp => 1)) - .OrderBy(kvp => kvp.Value); - - var totalVotesCast = results.Sum(kvp => kvp.Value); - if (totalVotesCast == 0) - { - await ch.SendMessage("📄 **No votes have been cast.**").ConfigureAwait(false); - return; - } - var closeMessage = $"--------------**POLL CLOSED**--------------\n" + - $"📄 , here are the results:\n"; - closeMessage = results.Aggregate(closeMessage, (current, kvp) => current + $"`{kvp.Key}.` **[{answers[kvp.Key - 1]}]**" + - $" has {kvp.Value} votes." + - $"({kvp.Value * 1.0f / totalVotesCast * 100}%)\n"); - - await ch.SendMessage($"📄 **Total votes cast**: {totalVotesCast}\n{closeMessage}").ConfigureAwait(false); - } - catch (Exception ex) - { - Console.WriteLine($"Error in poll game {ex}"); - } - } - - private async void Vote(object sender, MessageEventArgs e) - { - try - { - if (!e.Channel.IsPrivate) - return; - if (participants.ContainsKey(e.User)) - return; - - int vote; - if (!int.TryParse(e.Message.Text, out vote)) return; - if (vote < 1 || vote > answers.Length) - return; - if (participants.TryAdd(e.User, vote)) - { - await e.User.SendMessage($"Thanks for voting **{e.User.Name}**.").ConfigureAwait(false); - } - } - catch { } - } - } -} diff --git a/NadekoBot/Modules/Games/Commands/SpeedTyping.cs b/NadekoBot/Modules/Games/Commands/SpeedTyping.cs deleted file mode 100644 index 07100820..00000000 --- a/NadekoBot/Modules/Games/Commands/SpeedTyping.cs +++ /dev/null @@ -1,195 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.DataModels; -using NadekoBot.Extensions; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Games.Commands -{ - - public static class SentencesProvider - { - internal static string GetRandomSentence() - { - var data = DbHandler.Instance.GetAllRows(); - try - { - return data.ToList()[new Random().Next(0, data.Count())].Text; - } - catch - { - return "Failed retrieving data from parse. Owner didn't add any articles to type using `typeadd`."; - } - } - } - - public class TypingGame - { - public const float WORD_VALUE = 4.5f; - private readonly Channel channel; - public string CurrentSentence; - public bool IsActive; - private readonly Stopwatch sw; - private readonly List finishedUserIds; - - public TypingGame(Channel channel) - { - this.channel = channel; - IsActive = false; - sw = new Stopwatch(); - finishedUserIds = new List(); - } - - public Channel Channell { get; internal set; } - - internal async Task Stop() - { - if (!IsActive) return false; - NadekoBot.Client.MessageReceived -= AnswerReceived; - finishedUserIds.Clear(); - IsActive = false; - sw.Stop(); - sw.Reset(); - await channel.Send("Typing contest stopped").ConfigureAwait(false); - return true; - } - - internal async Task Start() - { - while (true) - { - if (IsActive) return; // can't start running game - IsActive = true; - CurrentSentence = SentencesProvider.GetRandomSentence(); - var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f); - await channel.SendMessage($":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false); - - - var msg = await channel.SendMessage("Starting new typing contest in **3**...").ConfigureAwait(false); - await Task.Delay(1000).ConfigureAwait(false); - await msg.Edit("Starting new typing contest in **2**...").ConfigureAwait(false); - await Task.Delay(1000).ConfigureAwait(false); - await msg.Edit("Starting new typing contest in **1**...").ConfigureAwait(false); - await Task.Delay(1000).ConfigureAwait(false); - await msg.Edit($":book:**{CurrentSentence.Replace(" ", " \x200B")}**:book:").ConfigureAwait(false); - sw.Start(); - HandleAnswers(); - - while (i > 0) - { - await Task.Delay(1000).ConfigureAwait(false); - i--; - if (!IsActive) - return; - } - - await Stop().ConfigureAwait(false); - } - } - - private void HandleAnswers() - { - NadekoBot.Client.MessageReceived += AnswerReceived; - } - - private async void AnswerReceived(object sender, MessageEventArgs e) - { - try - { - if (e.Channel == null || e.Channel.Id != channel.Id || e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - - var guess = e.Message.RawText; - - var distance = CurrentSentence.LevenshteinDistance(guess); - var decision = Judge(distance, guess.Length); - if (decision && !finishedUserIds.Contains(e.User.Id)) - { - finishedUserIds.Add(e.User.Id); - await channel.Send($"{e.User.Mention} finished in **{sw.Elapsed.Seconds}** seconds with { distance } errors, **{ CurrentSentence.Length / WORD_VALUE / sw.Elapsed.Seconds * 60 }** WPM!").ConfigureAwait(false); - if (finishedUserIds.Count % 2 == 0) - { - await e.Channel.SendMessage($":exclamation: `A lot of people finished, here is the text for those still typing:`\n\n:book:**{CurrentSentence}**:book:").ConfigureAwait(false); - } - } - } - catch { } - } - - private bool Judge(int errors, int textLength) => errors <= textLength / 25; - - } - - internal class SpeedTyping : DiscordCommand - { - - public static ConcurrentDictionary RunningContests; - - public SpeedTyping(DiscordModule module) : base(module) - { - RunningContests = new ConcurrentDictionary(); - } - - public Func DoFunc() => - async e => - { - var game = RunningContests.GetOrAdd(e.User.Server.Id, id => new TypingGame(e.Channel)); - - if (game.IsActive) - { - await e.Channel.SendMessage( - $"Contest already running in " + - $"{game.Channell.Mention} channel.") - .ConfigureAwait(false); - } - else - { - await game.Start().ConfigureAwait(false); - } - }; - - private Func QuitFunc() => - async e => - { - TypingGame game; - if (RunningContests.TryRemove(e.User.Server.Id, out game)) - { - await game.Stop().ConfigureAwait(false); - return; - } - await e.Channel.SendMessage("No contest to stop on this channel.").ConfigureAwait(false); - }; - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "typestart") - .Description($"Starts a typing contest. | `{Prefix}typestart`") - .Do(DoFunc()); - - cgb.CreateCommand(Module.Prefix + "typestop") - .Description($"Stops a typing contest on the current channel. | `{Prefix}typestop`") - .Do(QuitFunc()); - - cgb.CreateCommand(Module.Prefix + "typeadd") - .Description($"Adds a new article to the typing contest. Owner only. | `{Prefix}typeadd wordswords`") - .Parameter("text", ParameterType.Unparsed) - .Do(async e => - { - if (!NadekoBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("text"))) return; - - DbHandler.Instance.Connection.Insert(new TypingArticle - { - Text = e.GetArg("text"), - DateAdded = DateTime.Now - }); - - await e.Channel.SendMessage("Added new article for typing game.").ConfigureAwait(false); - }); - } - } -} diff --git a/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs deleted file mode 100644 index 3f38529f..00000000 --- a/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ /dev/null @@ -1,155 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Games.Commands.Trivia -{ - internal class TriviaGame - { - private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1,1); - - private Server server { get; } - private Channel channel { get; } - - private int QuestionDurationMiliseconds { get; } = 30000; - private int HintTimeoutMiliseconds { get; } = 6000; - public bool ShowHints { get; set; } = true; - private CancellationTokenSource triviaCancelSource { get; set; } - - public TriviaQuestion CurrentQuestion { get; private set; } - public HashSet oldQuestions { get; } = new HashSet(); - - public ConcurrentDictionary Users { get; } = new ConcurrentDictionary(); - - public bool GameActive { get; private set; } = false; - public bool ShouldStopGame { get; private set; } - - public int WinRequirement { get; } = 10; - - public TriviaGame(CommandEventArgs e, bool showHints, int winReq = 10) - { - ShowHints = showHints; - server = e.Server; - channel = e.Channel; - WinRequirement = winReq; - Task.Run(StartGame); - } - - private async Task StartGame() - { - while (!ShouldStopGame) - { - // reset the cancellation source - triviaCancelSource = new CancellationTokenSource(); - var token = triviaCancelSource.Token; - // load question - CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions); - if (CurrentQuestion == null) - { - await channel.SendMessage($":exclamation: Failed loading a trivia question").ConfigureAwait(false); - await End().ConfigureAwait(false); - return; - } - oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again - //sendquestion - await channel.SendMessage($":question: **{CurrentQuestion.Question}**").ConfigureAwait(false); - - //receive messages - NadekoBot.Client.MessageReceived += PotentialGuess; - - //allow people to guess - GameActive = true; - - try - { - //hint - await Task.Delay(HintTimeoutMiliseconds, token).ConfigureAwait(false); - if (ShowHints) - await channel.SendMessage($":exclamation:**Hint:** {CurrentQuestion.GetHint()}").ConfigureAwait(false); - - //timeout - await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, token).ConfigureAwait(false); - - } - catch (TaskCanceledException) { } //means someone guessed the answer - GameActive = false; - if (!triviaCancelSource.IsCancellationRequested) - await channel.Send($":clock2: :question: **Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); - NadekoBot.Client.MessageReceived -= PotentialGuess; - // load next question if game is still running - await Task.Delay(2000).ConfigureAwait(false); - } - await End().ConfigureAwait(false); - } - - private async Task End() - { - ShouldStopGame = true; - await channel.SendMessage("**Trivia game ended**\n" + GetLeaderboard()).ConfigureAwait(false); - TriviaGame throwAwayValue; - TriviaCommands.RunningTrivias.TryRemove(server.Id, out throwAwayValue); - } - - public async Task StopGame() - { - if (!ShouldStopGame) - await channel.SendMessage(":exclamation: Trivia will stop after this question.").ConfigureAwait(false); - ShouldStopGame = true; - } - - private async void PotentialGuess(object sender, MessageEventArgs e) - { - try - { - if (e.Channel.IsPrivate) return; - if (e.Server != server) return; - if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; - - var guess = false; - await _guessLock.WaitAsync().ConfigureAwait(false); - try - { - if (GameActive && CurrentQuestion.IsAnswerCorrect(e.Message.Text) && !triviaCancelSource.IsCancellationRequested) - { - Users.TryAdd(e.User, 0); //add if not exists - Users[e.User]++; //add 1 point to the winner - guess = true; - } - } - finally { _guessLock.Release(); } - if (!guess) return; - triviaCancelSource.Cancel(); - await channel.SendMessage($"☑️ {e.User.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); - if (Users[e.User] != WinRequirement) return; - ShouldStopGame = true; - await channel.Send($":exclamation: We have a winner! It's {e.User.Mention}.").ConfigureAwait(false); - // add points to the winner - await FlowersHandler.AddFlowersAsync(e.User, "Won Trivia", 2).ConfigureAwait(false); - } - catch { } - } - - public string GetLeaderboard() - { - if (Users.Count == 0) - return ""; - - var sb = new StringBuilder(); - sb.Append("**Leaderboard:**\n-----------\n"); - - foreach (var kvp in Users.OrderBy(kvp => kvp.Value)) - { - sb.AppendLine($"**{kvp.Key.Name}** has {kvp.Value} points".ToString().SnPl(kvp.Value)); - } - - return sb.ToString(); - } - } -} diff --git a/NadekoBot/Modules/Games/Commands/TriviaCommand.cs b/NadekoBot/Modules/Games/Commands/TriviaCommand.cs deleted file mode 100644 index 3a24eebb..00000000 --- a/NadekoBot/Modules/Games/Commands/TriviaCommand.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Games.Commands.Trivia; -using System; -using System.Collections.Concurrent; -using System.Linq; - -namespace NadekoBot.Modules.Games.Commands -{ - internal class TriviaCommands : DiscordCommand - { - public static ConcurrentDictionary RunningTrivias = new ConcurrentDictionary(); - - public TriviaCommands(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "t") - .Description($"Starts a game of trivia. You can add nohint to prevent hints." + - "First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question." + - $" |`{Module.Prefix}t nohint` or `{Module.Prefix}t 5 nohint`") - .Parameter("args", ParameterType.Multiple) - .Do(async e => - { - TriviaGame trivia; - if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) - { - var showHints = !e.Args.Contains("nohint"); - var number = e.Args.Select(s => - { - int num; - return new Tuple(int.TryParse(s, out num), num); - }).Where(t => t.Item1).Select(t => t.Item2).FirstOrDefault(); - if (number < 3) - { - await e.Channel.SendMessage("Number too small."); - return; - } - var triviaGame = new TriviaGame(e, showHints, number == 0 ? 10 : number); - if (RunningTrivias.TryAdd(e.Server.Id, triviaGame)) - await e.Channel.SendMessage($"**Trivia game started! {triviaGame.WinRequirement} points needed to win.**").ConfigureAwait(false); - else - await triviaGame.StopGame().ConfigureAwait(false); - } - else - await e.Channel.SendMessage("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "tl") - .Description($"Shows a current trivia leaderboard. | `{Prefix}tl`") - .Do(async e => - { - TriviaGame trivia; - if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) - await e.Channel.SendMessage(trivia.GetLeaderboard()).ConfigureAwait(false); - else - await e.Channel.SendMessage("No trivia is running on this server.").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "tq") - .Description($"Quits current trivia after current question. | `{Prefix}tq`") - .Do(async e => - { - TriviaGame trivia; - if (RunningTrivias.TryGetValue(e.Server.Id, out trivia)) - { - await trivia.StopGame().ConfigureAwait(false); - } - else - await e.Channel.SendMessage("No trivia is running on this server.").ConfigureAwait(false); - }); - } - } -} diff --git a/NadekoBot/Modules/Games/GamesModule.cs b/NadekoBot/Modules/Games/GamesModule.cs deleted file mode 100644 index acdaf1b3..00000000 --- a/NadekoBot/Modules/Games/GamesModule.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Extensions; -using NadekoBot.Modules.Games.Commands; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Linq; - -namespace NadekoBot.Modules.Games -{ - internal class GamesModule : DiscordModule - { - private readonly Random rng = new Random(); - - public GamesModule() - { - commands.Add(new TriviaCommands(this)); - commands.Add(new SpeedTyping(this)); - commands.Add(new PollCommand(this)); - commands.Add(new PlantPick(this)); - commands.Add(new Bomberman(this)); - commands.Add(new Leet(this)); - //commands.Add(new BetrayGame(this)); - - } - - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Games; - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand(Prefix + "choose") - .Description($"Chooses a thing from a list of things | `{Prefix}choose Get up;Sleep;Sleep more`") - .Parameter("list", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("list"); - if (string.IsNullOrWhiteSpace(arg)) - return; - var list = arg.Split(';'); - if (list.Count() < 2) - return; - await e.Channel.SendMessage(list[rng.Next(0, list.Length)]).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "8ball") - .Description($"Ask the 8ball a yes/no question. | `{Prefix}8ball should i do something`") - .Parameter("question", ParameterType.Unparsed) - .Do(async e => - { - var question = e.GetArg("question"); - if (string.IsNullOrWhiteSpace(question)) - return; - try - { - await e.Channel.SendMessage( - $":question: `Question` __**{question}**__ \n🎱 `8Ball Answers` __**{NadekoBot.Config._8BallResponses[rng.Next(0, NadekoBot.Config._8BallResponses.Length)]}**__") - .ConfigureAwait(false); - } - catch { } - }); - - cgb.CreateCommand(Prefix + "rps") - .Description($"Play a game of rocket paperclip scissors with Nadeko. | `{Prefix}rps scissors`") - .Parameter("input", ParameterType.Required) - .Do(async e => - { - var input = e.GetArg("input").Trim(); - int pick; - switch (input) - { - case "r": - case "rock": - case "rocket": - pick = 0; - break; - case "p": - case "paper": - case "paperclip": - pick = 1; - break; - case "scissors": - case "s": - pick = 2; - break; - default: - return; - } - var nadekoPick = new Random().Next(0, 3); - var msg = ""; - if (pick == nadekoPick) - msg = $"It's a draw! Both picked :{GetRPSPick(pick)}:"; - else if ((pick == 0 && nadekoPick == 1) || - (pick == 1 && nadekoPick == 2) || - (pick == 2 && nadekoPick == 0)) - msg = $"{NadekoBot.BotMention} won! :{GetRPSPick(nadekoPick)}: beats :{GetRPSPick(pick)}:"; - else - msg = $"{e.User.Mention} won! :{GetRPSPick(pick)}: beats :{GetRPSPick(nadekoPick)}:"; - - await e.Channel.SendMessage(msg).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "linux") - .Description($"Prints a customizable Linux interjection | `{Prefix}linux Spyware Windows`") - .Parameter("gnu", ParameterType.Required) - .Parameter("linux", ParameterType.Required) - .Do(async e => - { - var guhnoo = e.Args[0]; - var loonix = e.Args[1]; - - await e.Channel.SendMessage( -$@" -I'd just like to interject for moment. What you're refering to as {loonix}, is in fact, {guhnoo}/{loonix}, or as I've recently taken to calling it, {guhnoo} plus {loonix}. {loonix} is not an operating system unto itself, but rather another free component of a fully functioning {guhnoo} system made useful by the {guhnoo} corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. - -Many computer users run a modified version of the {guhnoo} system every day, without realizing it. Through a peculiar turn of events, the version of {guhnoo} which is widely used today is often called {loonix}, and many of its users are not aware that it is basically the {guhnoo} system, developed by the {guhnoo} Project. - -There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}. -").ConfigureAwait(false); - }); - }); - } - - private string GetRPSPick(int i) - { - if (i == 0) - return "rocket"; - else if (i == 1) - return "paperclip"; - else - return "scissors"; - } - } -} diff --git a/NadekoBot/Modules/Help/Commands/HelpCommand.cs b/NadekoBot/Modules/Help/Commands/HelpCommand.cs deleted file mode 100644 index 9d41d7bd..00000000 --- a/NadekoBot/Modules/Help/Commands/HelpCommand.cs +++ /dev/null @@ -1,142 +0,0 @@ -// -// _oo0oo_ -// o8888888o -// 88" . "88 -// (| -_- |) -// 0\ = /0 -// ___/`---'\___ -// .' \\| |// '. -// / \\||| : |||// \ -// / _||||| -:- |||||- \ -// | | \\\ - /// | | -// | \_| ''\---/'' |_/ | -// \ .-\__ '-' ___/-. / -// ___'. .' /--.--\ `. .'___ -// ."" '< `.___\_<|>_/___.' >' "". -// | | : `- \`.;`\ _ /`;.`/ - ` : | | -// \ \ `_. \_ __\ /__ _/ .-` / / -// =====`-.____`.___ \_____/___.-`___.-'===== -// `=---=' -// -// -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// -// 佛祖保佑 永无BUG -// -// -using Discord.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace NadekoBot.Classes.Help.Commands -{ - internal class HelpCommand : DiscordCommand - { - public Func HelpFunc() => async e => - { - var comToFind = e.GetArg("command")?.ToLowerInvariant(); - if (string.IsNullOrWhiteSpace(comToFind)) - { - await e.User.Send(HelpString).ConfigureAwait(false); - return; - } - await Task.Run(async () => - { - var com = NadekoBot.Client.GetService().AllCommands - .FirstOrDefault(c => c.Text.ToLowerInvariant().Equals(comToFind) || - c.Aliases.Select(a => a.ToLowerInvariant()).Contains(comToFind)); - - var str = ""; - var alias = com.Aliases.FirstOrDefault(); - if (alias != null) - str = $" / `{ com.Aliases.FirstOrDefault()}`"; - if (com != null) - await e.Channel.SendMessage($@"**__Help for:__ `{com.Text}`**" + str + $"\n**Desc:** {new Regex(@"\|").Replace(com.Description, "\n**Usage:**", 1)}").ConfigureAwait(false); - }).ConfigureAwait(false); - }; - public static string HelpString { - get { - var str = !string.IsNullOrWhiteSpace(NadekoBot.Creds.ClientId) && !NadekoBot.Config.DontJoinServers - ? String.Format("To add me to your server, use this link -> \n", NadekoBot.Creds.ClientId) - : ""; - return str + String.Format(NadekoBot.Config.HelpString, NadekoBot.Config.CommandPrefixes.Help); - } - } - - public static string DMHelpString => NadekoBot.Config.DMHelpString; - - public Action DoGitFunc() => e => - { - var helpstr = new StringBuilder(); - - var lastCategory = ""; - foreach (var com in NadekoBot.Client.GetService().AllCommands) - { - if (com.Category != lastCategory) - { - helpstr.AppendLine("\n### " + com.Category + " "); - helpstr.AppendLine("Command and aliases | Description | Usage"); - helpstr.AppendLine("----------------|--------------|-------"); - lastCategory = com.Category; - } - helpstr.AppendLine($"`{com.Text}`{string.Concat(com.Aliases.Select(a => $", `{a}`"))} | {com.Description}"); - } - helpstr = helpstr.Replace(NadekoBot.BotMention, "@BotName"); -#if DEBUG - File.WriteAllText("../../../docs/Commands List.md", helpstr.ToString()); -#else - File.WriteAllText("commandlist.md", helpstr.ToString()); -#endif - }; - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "h") - .Alias(Module.Prefix + "help", NadekoBot.BotMention + " help", NadekoBot.BotMention + " h", "~h") - .Description($"Either shows a help for a single command, or PMs you help link if no arguments are specified. | `{Prefix}h !m q` or just `{Prefix}h` ") - .Parameter("command", ParameterType.Unparsed) - .Do(HelpFunc()); - cgb.CreateCommand(Module.Prefix + "hgit") - .Description($"Generates the commandlist.md file. **Bot Owner Only!** | `{Prefix}hgit`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(DoGitFunc()); - cgb.CreateCommand(Module.Prefix + "readme") - .Alias(Module.Prefix + "guide") - .Description($"Sends a readme and a guide links to the channel. | `{Prefix}readme` or `{Prefix}guide`") - .Do(async e => - await e.Channel.SendMessage( -@"**LIST OF COMMANDS**: -**Hosting Guides and docs can be found here**: ").ConfigureAwait(false)); - - cgb.CreateCommand(Module.Prefix + "donate") - .Alias("~donate") - .Description($"Instructions for helping the project! | `{Prefix}donate` or `~donate`") - .Do(async e => - { - await e.Channel.SendMessage( -$@"You can support the project on patreon. or -You can send donations to `nadekodiscordbot@gmail.com` -Don't forget to leave your discord name or id in the message. - -**Thank you** ♥️").ConfigureAwait(false); - }); - } - - private static string PrintCommandHelp(Command com) - { - var str = "`" + com.Text + "`"; - str = com.Aliases.Aggregate(str, (current, a) => current + (", `" + a + "`")); - str += " **Description:** " + com.Description + "\n"; - return str; - } - - public HelpCommand(DiscordModule module) : base(module) { } - } -} diff --git a/NadekoBot/Modules/Help/HelpModule.cs b/NadekoBot/Modules/Help/HelpModule.cs deleted file mode 100644 index 45e4dc76..00000000 --- a/NadekoBot/Modules/Help/HelpModule.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.Classes.Help.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using System.Linq; - -namespace NadekoBot.Modules.Help -{ - internal class HelpModule : DiscordModule - { - - public HelpModule() - { - commands.Add(new HelpCommand(this)); - } - - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Help; - - - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - cgb.AddCheck(PermissionChecker.Instance); - commands.ForEach(com => com.Init(cgb)); - - cgb.CreateCommand(Prefix + "modules") - .Alias(".modules") - .Description($"List all bot modules. | `{Prefix}modules` or `.modules`") - .Do(async e => - { - await e.Channel.SendMessage("`List of modules:` \n• " + string.Join("\n• ", NadekoBot.Client.GetService().Modules.Select(m => m.Name)) + $"\n`Type \"{Prefix}commands module_name\" to get a list of commands in that module.`") - .ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "commands") - .Alias(".commands") - .Description($"List all of the bot's commands from a certain module. | `{Prefix}commands` or `.commands`") - .Parameter("module", ParameterType.Unparsed) - .Do(async e => - { - var module = e.GetArg("module")?.Trim().ToLower(); - if (string.IsNullOrWhiteSpace(module)) - return; - var cmds = NadekoBot.Client.GetService().AllCommands - .Where(c => c.Category.ToLower() == module) - .OrderBy(c=>c.Text) - .AsEnumerable(); - var cmdsArray = cmds as Command[] ?? cmds.ToArray(); - if (!cmdsArray.Any()) - { - await e.Channel.SendMessage("That module does not exist.").ConfigureAwait(false); - return; - } - if (module != "customreactions" && module != "conversations") - { - await e.Channel.SendMessage("`List Of Commands:`\n" + SearchHelper.ShowInPrettyCode(cmdsArray, - el => $"{el.Text,-15}{"[" + el.Aliases.FirstOrDefault() + "]",-8}")) - .ConfigureAwait(false); - } - else - { - await e.Channel.SendMessage("`List Of Commands:`\n• " + string.Join("\n• ", cmdsArray.Select(c => $"{c.Text}"))); - } - await e.Channel.SendMessage($"`You can type \"{Prefix}h command_name\" to see the help about that specific command.`").ConfigureAwait(false); - }); - }); - } - } -} diff --git a/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs b/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs deleted file mode 100644 index c4a95daa..00000000 --- a/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Music.Classes -{ - - /// - /// 💩 - /// - public class PoopyBuffer - { - - private readonly byte[] ringBuffer; - - public int WritePosition { get; private set; } = 0; - public int ReadPosition { get; private set; } = 0; - - public int ContentLength => (WritePosition >= ReadPosition ? - WritePosition - ReadPosition : - (BufferSize - ReadPosition) + WritePosition); - - public int BufferSize { get; } - - private readonly SemaphoreSlim readWriteLock = new SemaphoreSlim(1, 1); - - public PoopyBuffer(int size) - { - if (size <= 0) - throw new ArgumentException(); - BufferSize = size; - ringBuffer = new byte[size]; - } - - public Task ReadAsync(byte[] buffer, int count) - { - return Task.Run(async () => - { - if (buffer.Length < count) - throw new ArgumentException(); - //Console.WriteLine($"***\nRead: {ReadPosition}\nWrite: {WritePosition}\nContentLength:{ContentLength}\n***"); - await readWriteLock.WaitAsync().ConfigureAwait(false); - try - { - //read as much as you can if you're reading too much - if (count > ContentLength) - count = ContentLength; - //if nothing to read, return 0 - if (WritePosition == ReadPosition) - return 0; - // if buffer is in the "normal" state, just read - if (WritePosition > ReadPosition) - { - Buffer.BlockCopy(ringBuffer, ReadPosition, buffer, 0, count); - ReadPosition += count; - //Console.WriteLine($"Read only normally1 {count}[{ReadPosition - count} to {ReadPosition}]"); - return count; - } - //else ReadPos buffer.Length) - throw new ArgumentException(); - while (ContentLength + count > BufferSize) - { - await Task.Delay(20, cancelToken).ConfigureAwait(false); - if (cancelToken.IsCancellationRequested) - 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 - // *unless its multithreaded or task is not awaited - await readWriteLock.WaitAsync().ConfigureAwait(false); - try - { - // if i can just write without hitting buffer.length, do it - if (WritePosition + count < BufferSize) - { - Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, count); - WritePosition += count; - //Console.WriteLine($"Wrote only normally {count}[{WritePosition - count} to {WritePosition}]"); - return; - } - // otherwise, i have to write to the end, then write the rest from the start - - var wroteNormaly = BufferSize - WritePosition; - Buffer.BlockCopy(buffer, 0, ringBuffer, WritePosition, wroteNormaly); - - //Console.WriteLine($"Wrote normally {wroteNormaly}[{WritePosition} to {BufferSize}]"); - - var wroteFromStart = count - wroteNormaly; - Buffer.BlockCopy(buffer, wroteNormaly, ringBuffer, 0, wroteFromStart); - - //Console.WriteLine($"and from start {wroteFromStart} [0 to {wroteFromStart}"); - - WritePosition = wroteFromStart; - } - finally { readWriteLock.Release(); } - }); - } - } -} diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs deleted file mode 100644 index a4c40bd3..00000000 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ /dev/null @@ -1,900 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.DataModels; -using NadekoBot.Extensions; -using NadekoBot.Modules.Music.Classes; -using NadekoBot.Modules.Permissions.Classes; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Music -{ - internal class MusicModule : DiscordModule - { - public static ConcurrentDictionary MusicPlayers = new ConcurrentDictionary(); - - public const string MusicDataPath = "data/musicdata"; - - 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 void Install(ModuleManager manager) - { - var client = NadekoBot.Client; - - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand(Prefix + "next") - .Alias(Prefix + "n") - .Alias(Prefix + "skip") - .Description($"Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `{Prefix}n`") - .Do(e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; - if (musicPlayer.PlaybackVoiceChannel == e.User.VoiceChannel) - musicPlayer.Next(); - }); - - cgb.CreateCommand(Prefix + "stop") - .Alias(Prefix + "s") - .Description($"Stops the music and clears the playlist. Stays in the channel. | `{Prefix}s`") - .Do(e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; - if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) - { - musicPlayer.Autoplay = false; - musicPlayer.Stop(); - } - }); - - cgb.CreateCommand(Prefix + "destroy") - .Alias(Prefix + "d") - .Description("Completely stops the music and unbinds the bot from the channel. " + - $"(may cause weird behaviour) | `{Prefix}d`") - .Do(e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return; - if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) - musicPlayer.Destroy(); - }); - - cgb.CreateCommand(Prefix + "pause") - .Alias(Prefix + "p") - .Description($"Pauses or Unpauses the song. | `{Prefix}p`") - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; - if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - musicPlayer.TogglePause(); - if (musicPlayer.Paused) - await e.Channel.SendMessage("🎵`Music Player paused.`").ConfigureAwait(false); - else - await e.Channel.SendMessage("🎵`Music Player unpaused.`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "queue") - .Alias(Prefix + "q") - .Alias(Prefix + "yq") - .Description("Queue a song using keywords or a link. Bot will join your voice channel." + - $"**You must be in a voice channel**. | `{Prefix}q Dream Of Venice`") - .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - await QueueSong(e.User, e.Channel, e.User.VoiceChannel, e.GetArg("query")).ConfigureAwait(false); - if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) - { - await Task.Delay(10000).ConfigureAwait(false); - await e.Message.Delete().ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "soundcloudqueue") - .Alias(Prefix + "sq") - .Description("Queue a soundcloud song using keywords. Bot will join your voice channel." + - $"**You must be in a voice channel**. | `{Prefix}sq Dream Of Venice`") - .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - await QueueSong(e.User, e.Channel, e.User.VoiceChannel, e.GetArg("query"), musicType: MusicType.Soundcloud).ConfigureAwait(false); - if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) - { - await Task.Delay(10000).ConfigureAwait(false); - await e.Message.Delete().ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "listqueue") - .Alias(Prefix + "lq") - .Description($"Lists 15 currently queued songs per page. Default page is 1. | `{Prefix}lq` or `{Prefix}lq 2`") - .Parameter("page", ParameterType.Optional) - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - { - await e.Channel.SendMessage("🎵 No active music player.").ConfigureAwait(false); - return; - } - - int page; - if (!int.TryParse(e.GetArg("page"), out page) || page <= 0) - { - page = 1; - } - - var currentSong = musicPlayer.CurrentSong; - if (currentSong == null) - return; - var toSend = $"🎵`Now Playing` {currentSong.PrettyName} " + $"{currentSong.PrettyCurrentTime()}\n"; - if (musicPlayer.RepeatSong) - toSend += "🔂"; - else if (musicPlayer.RepeatPlaylist) - toSend += "🔁"; - toSend += $" **{musicPlayer.Playlist.Count}** `tracks currently queued. Showing page {page}` "; - if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) - toSend += "**Song queue is full!**\n"; - else - toSend += "\n"; - const int itemsPerPage = 15; - int startAt = itemsPerPage * (page - 1); - var number = 1 + startAt; - await e.Channel.SendMessage(toSend + string.Join("\n", musicPlayer.Playlist.Skip(startAt).Take(15).Select(v => $"`{number++}.` {v.PrettyName}"))).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "nowplaying") - .Alias(Prefix + "np") - .Description($"Shows the song currently playing. | `{Prefix}np`") - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - var currentSong = musicPlayer.CurrentSong; - if (currentSong == null) - return; - await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + - $"{currentSong.PrettyCurrentTime()}").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "volume") - .Alias(Prefix + "vol") - .Description($"Sets the music volume 0-100% | `{Prefix}vol 50`") - .Parameter("val", ParameterType.Required) - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - var arg = e.GetArg("val"); - int volume; - if (!int.TryParse(arg, out volume)) - { - await e.Channel.SendMessage("Volume number invalid.").ConfigureAwait(false); - return; - } - volume = musicPlayer.SetVolume(volume); - await e.Channel.SendMessage($"🎵 `Volume set to {volume}%`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "defvol") - .Alias(Prefix + "dv") - .Description("Sets the default music volume when music playback is started (0-100)." + - $" Persists through restarts. | `{Prefix}dv 80`") - .Parameter("val", ParameterType.Required) - .Do(async e => - { - var arg = e.GetArg("val"); - float volume; - if (!float.TryParse(arg, out volume) || volume < 0 || volume > 100) - { - await e.Channel.SendMessage("Volume number invalid.").ConfigureAwait(false); - return; - } - var conf = SpecificConfigurations.Default.Of(e.Server.Id); - conf.DefaultMusicVolume = volume / 100; - await e.Channel.SendMessage($"🎵 `Default volume set to {volume}%`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "mute") - .Alias(Prefix + "min") - .Description($"Sets the music volume to 0% | `{Prefix}min`") - .Do(e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - musicPlayer.SetVolume(0); - }); - - cgb.CreateCommand(Prefix + "max") - .Description($"Sets the music volume to 100%. | `{Prefix}max`") - .Do(e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - musicPlayer.SetVolume(100); - }); - - cgb.CreateCommand(Prefix + "half") - .Description($"Sets the music volume to 50%. | `{Prefix}half`") - .Do(e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - musicPlayer.SetVolume(50); - }); - - cgb.CreateCommand(Prefix + "shuffle") - .Alias(Prefix + "sh") - .Description($"Shuffles the current playlist. | `{Prefix}sh`") - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - if (musicPlayer.Playlist.Count < 2) - { - await e.Channel.SendMessage("💢 Not enough songs in order to perform the shuffle.").ConfigureAwait(false); - return; - } - - musicPlayer.Shuffle(); - await e.Channel.SendMessage("🎵 `Songs shuffled.`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "playlist") - .Alias(Prefix + "pl") - .Description($"Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `{Prefix}pl playlist link or name`") - .Parameter("playlist", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("playlist"); - if (string.IsNullOrWhiteSpace(arg)) - return; - if (e.User.VoiceChannel?.Server != e.Server) - { - await e.Channel.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.").ConfigureAwait(false); - return; - } - var plId = await SearchHelper.GetPlaylistIdByKeyword(arg).ConfigureAwait(false); - if (plId == null) - { - await e.Channel.SendMessage("No search results for that query."); - return; - } - var ids = await SearchHelper.GetVideoIDs(plId, 500).ConfigureAwait(false); - if (ids == null || ids.Count == 0) - { - await e.Channel.SendMessage($"🎵 `Failed to find any songs.`").ConfigureAwait(false); - return; - } - var idArray = ids as string[] ?? ids.ToArray(); - var count = idArray.Length; - var msg = - await e.Channel.SendMessage($"🎵 `Attempting to queue {count} songs".SnPl(count) + "...`").ConfigureAwait(false); - foreach (var id in idArray) - { - try - { - await QueueSong(e.User, e.Channel, e.User.VoiceChannel, id, true).ConfigureAwait(false); - } - catch (PlaylistFullException) - { break; } - catch { } - } - await msg.Edit("🎵 `Playlist queue complete.`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "soundcloudpl") - .Alias(Prefix + "scpl") - .Description($"Queue a soundcloud playlist using a link. | `{Prefix}scpl soundcloudseturl`") - .Parameter("pl", ParameterType.Unparsed) - .Do(async e => - { - var pl = e.GetArg("pl")?.Trim(); - - if (string.IsNullOrWhiteSpace(pl)) - return; - - var scvids = JObject.Parse(await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false))["tracks"].ToObject(); - await QueueSong(e.User, e.Channel, e.User.VoiceChannel, scvids[0].TrackLink).ConfigureAwait(false); - - MusicPlayer mp; - if (!MusicPlayers.TryGetValue(e.Server, out mp)) - return; - - foreach (var svideo in scvids.Skip(1)) - { - try - { - mp.AddSong(new Song(new Classes.SongInfo - { - Title = svideo.FullName, - Provider = "SoundCloud", - Uri = svideo.StreamLink, - ProviderType = MusicType.Normal, - Query = svideo.TrackLink, - }), e.User.Name); - } - catch (PlaylistFullException) { break; } - } - }); - - cgb.CreateCommand(Prefix + "localplaylst") - .Alias(Prefix + "lopl") - .Description($"Queues all songs from a directory. **Bot Owner Only!** | `{Prefix}lopl C:/music/classical`") - .Parameter("directory", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var arg = e.GetArg("directory"); - if (string.IsNullOrWhiteSpace(arg)) - return; - try - { - var fileEnum = new DirectoryInfo(arg).GetFiles() - .Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); - foreach (var file in fileEnum) - { - try - { - await QueueSong(e.User, e.Channel, e.User.VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false); - } - catch (PlaylistFullException) - { - break; - } - catch { } - } - await e.Channel.SendMessage("🎵 `Directory queue complete.`").ConfigureAwait(false); - } - catch { } - }); - - cgb.CreateCommand(Prefix + "radio").Alias(Prefix + "ra") - .Description($"Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `{Prefix}ra radio link here`") - .Parameter("radio_link", ParameterType.Required) - .Do(async e => - { - if (e.User.VoiceChannel?.Server != e.Server) - { - await e.Channel.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.").ConfigureAwait(false); - return; - } - await QueueSong(e.User, e.Channel, e.User.VoiceChannel, e.GetArg("radio_link"), musicType: MusicType.Radio).ConfigureAwait(false); - if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) - { - await Task.Delay(10000).ConfigureAwait(false); - await e.Message.Delete().ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "local") - .Alias(Prefix + "lo") - .Description($"Queues a local file by specifying a full path. **Bot Owner Only!** | `{Prefix}lo C:/music/mysong.mp3`") - .Parameter("path", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var arg = e.GetArg("path"); - if (string.IsNullOrWhiteSpace(arg)) - return; - await QueueSong(e.User, e.Channel, e.User.VoiceChannel, e.GetArg("path"), musicType: MusicType.Local).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "move") - .Alias(Prefix + "mv") - .Description($"Moves the bot to your voice channel. (works only if music is already playing) | `{Prefix}mv`") - .Do(e => - { - MusicPlayer musicPlayer; - var voiceChannel = e.User.VoiceChannel; - if (voiceChannel == null || voiceChannel.Server != e.Server || !MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - musicPlayer.MoveToVoiceChannel(voiceChannel); - }); - - cgb.CreateCommand(Prefix + "remove") - .Alias(Prefix + "rm") - .Description($"Remove a song by its # in the queue, or 'all' to remove whole queue. | `{Prefix}rm 5`") - .Parameter("num", ParameterType.Required) - .Do(async e => - { - var arg = e.GetArg("num"); - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - { - return; - } - if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - if (arg?.ToLower() == "all") - { - musicPlayer.ClearQueue(); - await e.Channel.SendMessage($"🎵`Queue cleared!`").ConfigureAwait(false); - return; - } - int num; - if (!int.TryParse(arg, out num)) - { - return; - } - if (num <= 0 || num > musicPlayer.Playlist.Count) - return; - var song = (musicPlayer.Playlist as List)?[num - 1]; - musicPlayer.RemoveSongAt(num - 1); - await e.Channel.SendMessage($"🎵**Track {song.PrettyName} at position `#{num}` has been removed.**").ConfigureAwait(false); - }); - - //var msRegex = new Regex(@"(?\d+)>(?\d+)", RegexOptions.Compiled); - cgb.CreateCommand(Prefix + "movesong") - .Alias(Prefix + "ms") - .Description($"Moves a song from one position to another. | `{Prefix} ms 5>3`") - .Parameter("fromto") - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - { - return; - } - var fromto = e.GetArg("fromto").Trim(); - var fromtoArr = fromto.Split('>'); - - int n1; - int n2; - - var playlist = musicPlayer.Playlist as List ?? musicPlayer.Playlist.ToList(); - - if (fromtoArr.Length != 2 || !int.TryParse(fromtoArr[0], out n1) || - !int.TryParse(fromtoArr[1], out n2) || n1 < 1 || n2 < 1 || n1 == n2 || - n1 > playlist.Count || n2 > playlist.Count) - { - await e.Channel.SendMessage("`Invalid input.`").ConfigureAwait(false); - return; - } - - var s = playlist[n1 - 1]; - playlist.Insert(n2 - 1, s); - var nn1 = n2 < n1 ? n1 : n1 - 1; - playlist.RemoveAt(nn1); - - await e.Channel.SendMessage($"🎵`Moved` {s.PrettyName} `from #{n1} to #{n2}`").ConfigureAwait(false); - - }); - - cgb.CreateCommand(Prefix + "setmaxqueue") - .Alias(Prefix + "smq") - .Description($"Sets a maximum queue size. Supply 0 or no argument to have no limit. | `{Prefix}smq 50` or `{Prefix}smq`") - .Parameter("size", ParameterType.Unparsed) - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - { - return; - } - - var sizeStr = e.GetArg("size")?.Trim(); - uint size = 0; - if (string.IsNullOrWhiteSpace(sizeStr) || !uint.TryParse(sizeStr, out size)) - { - size = 0; - } - - musicPlayer.MaxQueueSize = size; - await e.Channel.SendMessage($"🎵 `Max queue set to {(size == 0 ? ("unlimited") : size + " tracks")}`"); - }); - - cgb.CreateCommand(Prefix + "cleanup") - .Description($"Cleans up hanging voice connections. **Bot Owner Only!** | `{Prefix}cleanup`") - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(e => - { - foreach (var kvp in MusicPlayers) - { - var songs = kvp.Value.Playlist; - var currentSong = kvp.Value.CurrentSong; - if (songs.Count == 0 && currentSong == null) - { - MusicPlayer throwaway; - MusicPlayers.TryRemove(kvp.Key, out throwaway); - throwaway.Destroy(); - } - } - }); - - cgb.CreateCommand(Prefix + "reptcursong") - .Alias(Prefix + "rcs") - .Description($"Toggles repeat of current song. | `{Prefix}rcs`") - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - var currentSong = musicPlayer.CurrentSong; - if (currentSong == null) - return; - var currentValue = musicPlayer.ToggleRepeatSong(); - await e.Channel.SendMessage(currentValue ? - $"🎵🔂`Repeating track:`{currentSong.PrettyName}" : - $"🎵🔂`Current track repeat stopped.`") - .ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "rpeatplaylst") - .Alias(Prefix + "rpl") - .Description($"Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `{Prefix}rpl`") - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - var currentValue = musicPlayer.ToggleRepeatPlaylist(); - await e.Channel.SendMessage($"🎵🔁`Repeat playlist {(currentValue ? "enabled" : "disabled")}`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "save") - .Description($"Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `{Prefix}save classical1`") - .Parameter("name", ParameterType.Unparsed) - .Do(async e => - { - var name = e.GetArg("name")?.Trim(); - - if (string.IsNullOrWhiteSpace(name) || - name.Length > 20 || - name.Contains("-")) - return; - - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - - //to avoid concurrency issues - var currentPlaylist = new List(musicPlayer.Playlist); - var curSong = musicPlayer.CurrentSong; - if (curSong != null) - currentPlaylist.Insert(0, curSong); - - if (!currentPlaylist.Any()) - return; - - - var songInfos = currentPlaylist.Select(s => new DataModels.SongInfo - { - Provider = s.SongInfo.Provider, - ProviderType = (int)s.SongInfo.ProviderType, - Title = s.SongInfo.Title, - Uri = s.SongInfo.Uri, - Query = s.SongInfo.Query, - }).ToList(); - - var playlist = new MusicPlaylist - { - CreatorId = (long)e.User.Id, - CreatorName = e.User.Name, - Name = name.ToLowerInvariant(), - }; - DbHandler.Instance.SaveAll(songInfos); - DbHandler.Instance.Save(playlist); - DbHandler.Instance.Connection.InsertAll(songInfos.Select(s => new PlaylistSongInfo - { - PlaylistId = playlist.Id.Value, - SongInfoId = s.Id.Value - }), typeof(PlaylistSongInfo)); - - await e.Channel.SendMessage($"🎵 `Saved playlist as {name}-{playlist.Id}`").ConfigureAwait(false); - - }); - - cgb.CreateCommand(Prefix + "load") - .Description($"Loads a playlist under a certain name. | `{Prefix}load classical-1`") - .Parameter("name", ParameterType.Unparsed) - .Do(async e => - { - var voiceCh = e.User.VoiceChannel; - var textCh = e.Channel; - if (voiceCh == null || voiceCh.Server != textCh.Server) - { - await textCh.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining.").ConfigureAwait(false); - return; - } - var name = e.GetArg("name")?.Trim().ToLowerInvariant(); - - if (string.IsNullOrWhiteSpace(name)) - return; - - var parts = name.Split('-'); - if (parts.Length != 2) - return; - var playlistName = parts[0]; - - int playlistNumber; - if (!int.TryParse(parts[1], out playlistNumber)) - return; - - var playlist = DbHandler.Instance.FindOne( - p => p.Id == playlistNumber); - - if (playlist == null) - { - await e.Channel.SendMessage("Can't find playlist under that name.").ConfigureAwait(false); - return; - } - - var psis = DbHandler.Instance.FindAll(psi => - psi.PlaylistId == playlist.Id); - - var songInfos = psis.Select(psi => DbHandler.Instance - .FindOne(si => si.Id == psi.SongInfoId)); - - await e.Channel.SendMessage($"`Attempting to load {songInfos.Count()} songs`").ConfigureAwait(false); - foreach (var si in songInfos) - { - try - { - await QueueSong(e.User, textCh, voiceCh, si.Query, true, (MusicType)si.ProviderType).ConfigureAwait(false); - } - catch (PlaylistFullException) - { - break; - } - catch (Exception ex) - { - Console.WriteLine($"Failed QueueSong in load playlist. {ex}"); - } - } - }); - - cgb.CreateCommand(Prefix + "playlists") - .Alias(Prefix + "pls") - .Description($"Lists all playlists. Paginated. 20 per page. Default page is 0. |`{Prefix}pls 1`") - .Parameter("num", ParameterType.Optional) - .Do(e => - { - int num = 0; - int.TryParse(e.GetArg("num"), out num); - if (num < 0) - return; - var result = DbHandler.Instance.GetPlaylistData(num); - if (result.Count == 0) - e.Channel.SendMessage($"`No saved playlists found on page {num}`").ConfigureAwait(false); - else - e.Channel.SendMessage($"```js\n--- List of saved playlists ---\n\n" + string.Join("\n", result.Select(r => $"'{r.Name}-{r.Id}' by {r.Creator} ({r.SongCnt} songs)")) + $"\n\n --- Page {num} ---```").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "deleteplaylist") - .Alias(Prefix + "delpls") - .Description($"Deletes a saved playlist. Only if you made it or if you are the bot owner. | `{Prefix}delpls animu-5`") - .Parameter("pl", ParameterType.Required) - .Do(async e => - { - var pl = e.GetArg("pl").Trim().Split('-')[1]; - if (string.IsNullOrWhiteSpace(pl)) - return; - var plnum = int.Parse(pl); - if (NadekoBot.IsOwner(e.User.Id)) - DbHandler.Instance.Delete(plnum); - else - DbHandler.Instance.DeleteWhere(mp => mp.Id == plnum && (long)e.User.Id == mp.CreatorId); - await e.Channel.SendMessage("`Ok.` :ok:").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "goto") - .Description($"Goes to a specific time in seconds in a song. | `{Prefix}goto 30`") - .Parameter("time") - .Do(async e => - { - var skipToStr = e.GetArg("time")?.Trim(); - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) - return; - int skipTo; - if (!int.TryParse(skipToStr, out skipTo) || skipTo < 0) - return; - - var currentSong = musicPlayer.CurrentSong; - - if (currentSong == null) - return; - - //currentSong.PrintStatusMessage = false; - var gotoSong = currentSong.Clone(); - gotoSong.SkipTo = skipTo; - musicPlayer.AddSong(gotoSong, 0); - musicPlayer.Next(); - - var minutes = (skipTo / 60).ToString(); - var seconds = (skipTo % 60).ToString(); - - if (minutes.Length == 1) - minutes = "0" + minutes; - if (seconds.Length == 1) - seconds = "0" + seconds; - - await e.Channel.SendMessage($"`Skipped to {minutes}:{seconds}`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "getlink") - .Alias(Prefix + "gl") - .Description($"Shows a link to the song in the queue by index, or the currently playing song by default. | `{Prefix}gl`") - .Parameter("index", ParameterType.Optional) - .Do(async e => - { - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - int index; - string arg = e.GetArg("index")?.Trim(); - if (!string.IsNullOrEmpty(arg) && int.TryParse(arg, out index)) - { - - var selSong = musicPlayer.Playlist.DefaultIfEmpty(null).ElementAtOrDefault(index - 1); - if (selSong == null) - { - await e.Channel.SendMessage("Could not select song, likely wrong index"); - - } - else - { - await e.Channel.SendMessage($"🎶`Selected song {selSong.SongInfo.Title}:` <{selSong.SongInfo.Query}>").ConfigureAwait(false); - } - } - else - { - var curSong = musicPlayer.CurrentSong; - if (curSong == null) - return; - await e.Channel.SendMessage($"🎶`Current song:` <{curSong.SongInfo.Query}>").ConfigureAwait(false); - } - - }); - - cgb.CreateCommand(Prefix + "autoplay") - .Alias(Prefix + "ap") - .Description($"Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) | `{Prefix}ap`") - .Do(async e => - { - - MusicPlayer musicPlayer; - if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - return; - - if (!musicPlayer.ToggleAutoplay()) - await e.Channel.SendMessage("🎶`Autoplay disabled.`").ConfigureAwait(false); - else - await e.Channel.SendMessage("🎶`Autoplay enabled.`").ConfigureAwait(false); - }); - }); - } - - public static async Task QueueSong(User queuer, Channel textCh, Channel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) - { - if (voiceCh == null || voiceCh.Server != textCh.Server) - { - if (!silent) - await textCh.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining.").ConfigureAwait(false); - throw new ArgumentNullException(nameof(voiceCh)); - } - if (string.IsNullOrWhiteSpace(query) || query.Length < 3) - throw new ArgumentException("💢 Invalid query for queue song.", nameof(query)); - - var musicPlayer = MusicPlayers.GetOrAdd(textCh.Server, server => - { - float vol = SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume; - var mp = new MusicPlayer(voiceCh, vol); - - - Message playingMessage = null; - Message lastFinishedMessage = null; - mp.OnCompleted += async (s, song) => - { - if (song.PrintStatusMessage) - { - try - { - if (lastFinishedMessage != null) - await lastFinishedMessage.Delete().ConfigureAwait(false); - if (playingMessage != null) - await playingMessage.Delete().ConfigureAwait(false); - lastFinishedMessage = await textCh.SendMessage($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false); - if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube") - { - await QueueSong(queuer.Server.CurrentUser, textCh, voiceCh, (await SearchHelper.GetRelatedVideoIds(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false); - } - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - }; - mp.OnStarted += async (s, song) => - { - if (song.PrintStatusMessage) - { - var sender = s as MusicPlayer; - if (sender == null) - return; - - try - { - - var msgTxt = $"🎵`Playing`{song.PrettyName} `Vol: {(int)(sender.Volume * 100)}%`"; - playingMessage = await textCh.SendMessage(msgTxt).ConfigureAwait(false); - } - catch { } - } - }; - return mp; - }); - Song resolvedSong; - try - { - musicPlayer.ThrowIfQueueFull(); - resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false); - - musicPlayer.AddSong(resolvedSong, queuer.Name); - } - catch (PlaylistFullException) - { - await textCh.SendMessage($"🎵 `Queue is full at {musicPlayer.MaxQueueSize}/{musicPlayer.MaxQueueSize}.` "); - throw; - } - if (!silent) - { - 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 - Task.Run(async () => - { - await Task.Delay(10000).ConfigureAwait(false); - try - { - await queuedMessage.Delete().ConfigureAwait(false); - } - catch { } - }).ConfigureAwait(false); -#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed - } - } - } -} diff --git a/NadekoBot/Modules/NSFW/NSFWModule.cs b/NadekoBot/Modules/NSFW/NSFWModule.cs deleted file mode 100644 index ac688fb7..00000000 --- a/NadekoBot/Modules/NSFW/NSFWModule.cs +++ /dev/null @@ -1,127 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using Newtonsoft.Json.Linq; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.NSFW -{ - internal class NSFWModule : DiscordModule - { - - private readonly Random rng = new Random(); - - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.NSFW; - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - cgb.CreateCommand(Prefix + "hentai") - .Description($"Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}hentai yuri+kissing`") - .Parameter("tag", ParameterType.Unparsed) - .Do(async e => - { - var tag = e.GetArg("tag")?.Trim() ?? ""; - - var links = await Task.WhenAll(SearchHelper.GetGelbooruImageLink("rating%3Aexplicit+" + tag), SearchHelper.GetDanbooruImageLink("rating%3Aexplicit+" + tag)).ConfigureAwait(false); - - if (links.All(l => l == null)) - { - await e.Channel.SendMessage("`No results.`"); - return; - } - - await e.Channel.SendMessage(String.Join("\n\n", links)).ConfigureAwait(false); - }); - cgb.CreateCommand(Prefix + "danbooru") - .Description($"Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}danbooru yuri+kissing`") - .Parameter("tag", ParameterType.Unparsed) - .Do(async e => - { - var tag = e.GetArg("tag")?.Trim() ?? ""; - var link = await SearchHelper.GetDanbooruImageLink(tag).ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(link)) - await e.Channel.SendMessage("Search yielded no results ;("); - else - await e.Channel.SendMessage(link).ConfigureAwait(false); - }); - cgb.CreateCommand(Prefix + "gelbooru") - .Description($"Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}gelbooru yuri+kissing`") - .Parameter("tag", ParameterType.Unparsed) - .Do(async e => - { - var tag = e.GetArg("tag")?.Trim() ?? ""; - var link = await SearchHelper.GetGelbooruImageLink(tag).ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(link)) - await e.Channel.SendMessage("Search yielded no results ;("); - else - await e.Channel.SendMessage(link).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "rule34") - .Description($"Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}rule34 yuri+kissing`") - .Parameter("tag", ParameterType.Unparsed) - .Do(async e => - { - var tag = e.GetArg("tag")?.Trim() ?? ""; - var link = await SearchHelper.GetRule34ImageLink(tag).ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(link)) - await e.Channel.SendMessage("Search yielded no results ;("); - else - await e.Channel.SendMessage(link).ConfigureAwait(false); - }); - cgb.CreateCommand(Prefix + "e621") - .Description($"Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags. | `{Prefix}e621 yuri kissing`") - .Parameter("tag", ParameterType.Unparsed) - .Do(async e => - { - var tag = e.GetArg("tag")?.Trim() ?? ""; - await e.Channel.SendMessage(await SearchHelper.GetE621ImageLink(tag).ConfigureAwait(false)).ConfigureAwait(false); - }); - cgb.CreateCommand(Prefix + "cp") - .Description($"We all know where this will lead you to. | `{Prefix}cp`") - .Parameter("anything", ParameterType.Unparsed) - .Do(async e => - { - await e.Channel.SendMessage("http://i.imgur.com/MZkY1md.jpg").ConfigureAwait(false); - }); - cgb.CreateCommand(Prefix + "boobs") - .Description($"Real adult content. | `{Prefix}boobs`") - .Do(async e => - { - try - { - var obj = JArray.Parse(await SearchHelper.GetResponseStringAsync($"http://api.oboobs.ru/boobs/{rng.Next(0, 9380)}").ConfigureAwait(false))[0]; - await e.Channel.SendMessage($"http://media.oboobs.ru/{ obj["preview"].ToString() }").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 {ex.Message}").ConfigureAwait(false); - } - }); - cgb.CreateCommand(Prefix + "butts") - .Alias(Prefix + "ass", Prefix + "butt") - .Description($"Real adult content. | `{Prefix}butts` or `{Prefix}ass`") - .Do(async e => - { - try - { - var obj = JArray.Parse(await SearchHelper.GetResponseStringAsync($"http://api.obutts.ru/butts/{rng.Next(0, 3373)}").ConfigureAwait(false))[0]; - await e.Channel.SendMessage($"http://media.obutts.ru/{ obj["preview"].ToString() }").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 {ex.Message}").ConfigureAwait(false); - } - }); - }); - } - } -} diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs b/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs deleted file mode 100644 index 53c874c1..00000000 --- a/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.Commands.Permissions; -using NadekoBot.Classes.JSONModels; -using System; -using System.Collections.Concurrent; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Permissions.Classes -{ - - internal class PermissionChecker : IPermissionChecker - { - public static PermissionChecker Instance { get; } = new PermissionChecker(); - - private ConcurrentDictionary commandCooldowns = new ConcurrentDictionary(); - private ConcurrentDictionary timeBlackList { get; } = new ConcurrentDictionary(); - - static PermissionChecker() { } - private PermissionChecker() - { - Task.Run(async () => - { - while (true) - { - await Task.Delay(1000).ConfigureAwait(false); - timeBlackList.Clear(); - } - }); - } - - public bool CanRun(Command command, User user, Channel channel, out string error) - { - error = String.Empty; - - if (!NadekoBot.Ready) - return false; - - if (channel.IsPrivate || channel.Server == null) - return command.Category == "Help"; - - if (user == null) - return false; - - if (ConfigHandler.IsUserBlacklisted(user.Id) || - (!channel.IsPrivate && - (ConfigHandler.IsServerBlacklisted(channel.Server.Id) || ConfigHandler.IsChannelBlacklisted(channel.Id)))) - { - return false; - } - try - { - if (timeBlackList.ContainsKey(user.Id)) - return false; - } - catch { return false; } - - if (!channel.IsPrivate && !channel.Server.CurrentUser.GetPermissions(channel).SendMessages) - { - return false; - } - - timeBlackList.TryAdd(user.Id, true); - - ServerPermissions perms; - PermissionsHandler.PermissionsDict.TryGetValue(user.Server.Id, out perms); - - AddUserCooldown(user.Server.Id, user.Id, command.Text.ToLower()); - if (commandCooldowns.Keys.Contains(user.Server.Id + ":" + command.Text.ToLower())) - { - if (perms?.Verbose == true) - error = $"{user.Mention} You have a cooldown on that command."; - return false; - } - - try - { - //is it a permission command? - // if it is, check if the user has the correct role - // if yes return true, if no return false - if (command.Category == "Permissions") - { - Discord.Role role = null; - try - { - role = PermissionHelper.ValidateRole(user.Server, - PermissionsHandler.GetServerPermissionsRoleName(user.Server)); - } - catch { } - if (user.Server.Owner.Id == user.Id || (role != null && user.HasRole(role))) - return true; - throw new Exception($"You don't have the necessary role (**{(perms?.PermissionsControllerRole ?? "Nadeko")}**) to change permissions."); - } - - var permissionType = PermissionsHandler.GetPermissionBanType(command, user, channel); - - string msg; - - if (permissionType == PermissionsHandler.PermissionBanType.ServerBanModule && - command.Category.ToLower() == "nsfw") - msg = $"**{command.Category}** module has been banned from use on this **server**.\nNSFW module is disabled by default. Server owner can type `;sm nsfw enable` to enable it."; - else - switch (permissionType) - { - case PermissionsHandler.PermissionBanType.None: - return true; - case PermissionsHandler.PermissionBanType.ServerBanCommand: - msg = $"**{command.Text}** command has been banned from use on this **server**."; - break; - case PermissionsHandler.PermissionBanType.ServerBanModule: - msg = $"**{command.Category}** module has been banned from use on this **server**."; - break; - case PermissionsHandler.PermissionBanType.ChannelBanCommand: - msg = $"**{command.Text}** command has been banned from use on this **channel**."; - break; - case PermissionsHandler.PermissionBanType.ChannelBanModule: - msg = $"**{command.Category}** module has been banned from use on this **channel**."; - break; - case PermissionsHandler.PermissionBanType.RoleBanCommand: - msg = $"You do not have a **role** which permits you the usage of **{command.Text}** command."; - break; - case PermissionsHandler.PermissionBanType.RoleBanModule: - msg = $"You do not have a **role** which permits you the usage of **{command.Category}** module."; - break; - case PermissionsHandler.PermissionBanType.UserBanCommand: - msg = $"{user.Mention}, You have been banned from using **{command.Text}** command."; - break; - case PermissionsHandler.PermissionBanType.UserBanModule: - msg = $"{user.Mention}, You have been banned from using **{command.Category}** module."; - break; - default: - return true; - } - if (PermissionsHandler.PermissionsDict[user.Server.Id].Verbose) //if verbose - print errors - error = msg; - return false; - } - catch (Exception ex) - { - Console.WriteLine($"Exception in canrun: {ex}"); - try - { - if (perms != null && perms.Verbose) - //if verbose - print errors - error = ex.Message; - } - catch (Exception ex2) - { - Console.WriteLine($"SERIOUS PERMISSION ERROR {ex2}\n\nUser:{user} Server: {user?.Server?.Name}/{user?.Server?.Id}"); - } - return false; - } - } - - public void AddUserCooldown(ulong serverId, ulong userId, string commandName) - { - commandCooldowns.TryAdd(commandName, userId); - var tosave = serverId + ":" + commandName; - Task.Run(async () => - { - ServerPermissions perms; - PermissionsHandler.PermissionsDict.TryGetValue(serverId, out perms); - int cd; - if (!perms.CommandCooldowns.TryGetValue(commandName, out cd)) - { - return; - } - if (commandCooldowns.TryAdd(tosave, userId)) - { - await Task.Delay(cd * 1000); - ulong throwaway; - commandCooldowns.TryRemove(tosave, out throwaway); - } - - }); - } - } -} diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs b/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs deleted file mode 100644 index d4a07e21..00000000 --- a/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs +++ /dev/null @@ -1,101 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.Modules; -using System; -using System.Linq; - -namespace NadekoBot.Modules.Permissions.Classes -{ - internal static class PermissionHelper - { - public static bool ValidateBool(string passedArg) - { - if (string.IsNullOrWhiteSpace(passedArg)) - { - throw new ArgumentException("No value supplied! Missing argument"); - } - switch (passedArg.ToLower()) - { - case "1": - case "t": - case "true": - case "enable": - case "enabled": - case "allow": - case "unban": - return true; - case "0": - case "f": - case "false": - case "disable": - case "disabled": - case "disallow": - case "ban": - return false; - default: - throw new ArgumentException("Did not receive a valid boolean value"); - } - } - - internal static string ValidateModule(string mod) - { - if (string.IsNullOrWhiteSpace(mod)) - throw new ArgumentNullException(nameof(mod)); - - foreach (var m in NadekoBot.Client.GetService().Modules) - { - if (m.Name.ToLower().Equals(mod.Trim().ToLower())) - return m.Name; - } - throw new ArgumentException("That module does not exist."); - } - - internal static string ValidateCommand(string commandText) - { - if (string.IsNullOrWhiteSpace(commandText)) - throw new ArgumentNullException(nameof(commandText)); - - var normalizedCmdTxt = commandText.Trim().ToUpperInvariant(); - - foreach (var com in NadekoBot.Client.GetService().AllCommands) - { - if (com.Text.ToUpperInvariant().Equals(normalizedCmdTxt) || com.Aliases.Select(c => c.ToUpperInvariant()).Contains(normalizedCmdTxt)) - return com.Text; - } - throw new NullReferenceException("That command does not exist."); - } - - internal static Role ValidateRole(Server server, string roleName) - { - if (string.IsNullOrWhiteSpace(roleName)) - throw new ArgumentNullException(nameof(roleName)); - - if (roleName.Trim() == "everyone") - roleName = "@everyone"; - var role = server.FindRoles(roleName.Trim()).FirstOrDefault(); - if (role == null) - throw new NullReferenceException("That role does not exist."); - return role; - } - - internal static Channel ValidateChannel(Server server, string channelName) - { - if (string.IsNullOrWhiteSpace(channelName)) - throw new ArgumentNullException(nameof(channelName)); - var channel = server.FindChannels(channelName.Trim(), ChannelType.Text).FirstOrDefault(); - if (channel == null) - throw new NullReferenceException("That channel does not exist."); - return channel; - } - - internal static User ValidateUser(Server server, string userName) - { - if (string.IsNullOrWhiteSpace(userName)) - throw new ArgumentNullException(nameof(userName)); - var user = server.FindUsers(userName.Trim()).FirstOrDefault(); - if (user == null) - throw new NullReferenceException("That user does not exist."); - return user; - } - } -} diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs b/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs deleted file mode 100644 index 9c00c7c6..00000000 --- a/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs +++ /dev/null @@ -1,577 +0,0 @@ -using Discord; -using Discord.Commands; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Permissions.Classes -{ - public static class PermissionsHandler - { - public static ConcurrentDictionary PermissionsDict = - new ConcurrentDictionary(); - - public enum PermissionBanType - { - None, ServerBanCommand, ServerBanModule, - ChannelBanCommand, ChannelBanModule, RoleBanCommand, - RoleBanModule, UserBanCommand, UserBanModule - } - - - public static Task Initialize() => Task.Run(() => - { - Console.WriteLine("Reading from the permission files."); - Directory.CreateDirectory("data/permissions"); - foreach (var file in Directory.EnumerateFiles("data/permissions/")) - { - try - { - var strippedFileName = Path.GetFileNameWithoutExtension(file); - if (string.IsNullOrWhiteSpace(strippedFileName)) continue; - var id = ulong.Parse(strippedFileName); - var data = Newtonsoft.Json.JsonConvert.DeserializeObject(File.ReadAllText(file)); - PermissionsDict.TryAdd(id, data); - } - catch { } - } - Console.WriteLine("Permission initialization complete."); - }); - - internal static Permissions GetRolePermissionsById(Server server, ulong id) - { - ServerPermissions serverPerms; - if (!PermissionsDict.TryGetValue(server.Id, out serverPerms)) - return null; - - Permissions toReturn; - serverPerms.RolePermissions.TryGetValue(id, out toReturn); - return toReturn; - } - - internal static Permissions GetUserPermissionsById(Server server, ulong id) - { - ServerPermissions serverPerms; - if (!PermissionsDict.TryGetValue(server.Id, out serverPerms)) - return null; - - Permissions toReturn; - serverPerms.UserPermissions.TryGetValue(id, out toReturn); - return toReturn; - } - - internal static Permissions GetChannelPermissionsById(Server server, ulong id) - { - ServerPermissions serverPerms; - if (!PermissionsDict.TryGetValue(server.Id, out serverPerms)) - return null; - - Permissions toReturn; - serverPerms.ChannelPermissions.TryGetValue(id, out toReturn); - return toReturn; - } - - internal static Permissions GetServerPermissions(Server server) - { - ServerPermissions serverPerms; - return !PermissionsDict.TryGetValue(server.Id, out serverPerms) ? null : serverPerms.Permissions; - } - - internal static PermissionBanType GetPermissionBanType(Command command, User user, Channel channel) - { - var server = user.Server; - ServerPermissions serverPerms = PermissionsDict.GetOrAdd(server.Id, id => new ServerPermissions(id, server.Name)); - bool val; - Permissions perm; - //server - if (serverPerms.Permissions.Modules.TryGetValue(command.Category, out val) && val == false) - return PermissionBanType.ServerBanModule; - if (serverPerms.Permissions.Commands.TryGetValue(command.Text, out val) && val == false) - return PermissionBanType.ServerBanCommand; - //channel - if (serverPerms.ChannelPermissions.TryGetValue(channel.Id, out perm) && - perm.Modules.TryGetValue(command.Category, out val) && val == false) - return PermissionBanType.ChannelBanModule; - if (serverPerms.ChannelPermissions.TryGetValue(channel.Id, out perm) && - perm.Commands.TryGetValue(command.Text, out val) && val == false) - return PermissionBanType.ChannelBanCommand; - - //ROLE PART - TWO CASES - // FIRST CASE: - // IF EVERY ROLE USER HAS IS BANNED FROM THE MODULE, - // THAT MEANS USER CANNOT RUN THIS COMMAND - // IF AT LEAST ONE ROLE EXIST THAT IS NOT BANNED, - // USER CAN RUN THE COMMAND - var foundNotBannedRole = false; - foreach (var role in user.Roles) - { - //if every role is banned from using the module -> rolebanmodule - if (serverPerms.RolePermissions.TryGetValue(role.Id, out perm) && - perm.Modules.TryGetValue(command.Category, out val) && val == false) - continue; - foundNotBannedRole = true; - break; - } - if (!foundNotBannedRole) - return PermissionBanType.RoleBanModule; - - // SECOND CASE: - // IF EVERY ROLE USER HAS IS BANNED FROM THE COMMAND, - // THAT MEANS USER CANNOT RUN THAT COMMAND - // IF AT LEAST ONE ROLE EXISTS THAT IS NOT BANNED, - // USER CAN RUN THE COMMAND - foundNotBannedRole = false; - foreach (var role in user.Roles) - { - //if every role is banned from using the module -> rolebanmodule - if (serverPerms.RolePermissions.TryGetValue(role.Id, out perm) && - perm.Commands.TryGetValue(command.Text, out val) && val == false) - continue; - else - { - foundNotBannedRole = true; - break; - } - } - if (!foundNotBannedRole) - return PermissionBanType.RoleBanCommand; - - //user - if (serverPerms.UserPermissions.TryGetValue(user.Id, out perm) && - perm.Modules.TryGetValue(command.Category, out val) && val == false) - return PermissionBanType.UserBanModule; - if (serverPerms.UserPermissions.TryGetValue(user.Id, out perm) && - perm.Commands.TryGetValue(command.Text, out val) && val == false) - return PermissionBanType.UserBanCommand; - - return PermissionBanType.None; - } - - private static Task WriteServerToJson(ServerPermissions serverPerms) => Task.Run(() => - { - string pathToFile = $"data/permissions/{serverPerms.Id}.json"; - File.WriteAllText(pathToFile, - Newtonsoft.Json.JsonConvert.SerializeObject(serverPerms, Newtonsoft.Json.Formatting.Indented)); - }); - - public static Task WriteToJson() => Task.Run(() => - { - Directory.CreateDirectory("data/permissions/"); - foreach (var kvp in PermissionsDict) - { - WriteServerToJson(kvp.Value); - } - }); - - public static string GetServerPermissionsRoleName(Server server) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - return serverPerms.PermissionsControllerRole; - } - - internal static async Task SetPermissionsRole(Server server, string roleName) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - serverPerms.PermissionsControllerRole = roleName; - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - internal static async Task SetVerbosity(Server server, bool val) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - serverPerms.Verbose = val; - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - internal static async Task CopyRolePermissions(Role fromRole, Role toRole) - { - var server = fromRole.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - var from = GetRolePermissionsById(server, fromRole.Id); - if (from == null) - serverPerms.RolePermissions.Add(fromRole.Id, from = new Permissions(fromRole.Name)); - var to = GetRolePermissionsById(server, toRole.Id); - if (to == null) - serverPerms.RolePermissions.Add(toRole.Id, to = new Permissions(toRole.Name)); - - to.CopyFrom(from); - - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - internal static async Task CopyChannelPermissions(Channel fromChannel, Channel toChannel) - { - var server = fromChannel.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - var from = GetChannelPermissionsById(server, fromChannel.Id); - if (from == null) - serverPerms.ChannelPermissions.Add(fromChannel.Id, from = new Permissions(fromChannel.Name)); - var to = GetChannelPermissionsById(server, toChannel.Id); - if (to == null) - serverPerms.ChannelPermissions.Add(toChannel.Id, to = new Permissions(toChannel.Name)); - - to.CopyFrom(from); - - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - internal static async Task CopyUserPermissions(User fromUser, User toUser) - { - var server = fromUser.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - var from = GetUserPermissionsById(server, fromUser.Id); - if (from == null) - serverPerms.UserPermissions.Add(fromUser.Id, from = new Permissions(fromUser.Name)); - var to = GetUserPermissionsById(server, toUser.Id); - if (to == null) - serverPerms.UserPermissions.Add(toUser.Id, to = new Permissions(toUser.Name)); - - to.CopyFrom(from); - - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetServerModulePermission(Server server, string moduleName, bool value) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - var modules = serverPerms.Permissions.Modules; - if (modules.ContainsKey(moduleName)) - modules[moduleName] = value; - else - modules.TryAdd(moduleName, value); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetServerCommandPermission(Server server, string commandName, bool value) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - var commands = serverPerms.Permissions.Commands; - if (commands.ContainsKey(commandName)) - commands[commandName] = value; - else - commands.TryAdd(commandName, value); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetChannelModulePermission(Channel channel, string moduleName, bool value) - { - var server = channel.Server; - - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - if (!serverPerms.ChannelPermissions.ContainsKey(channel.Id)) - serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name)); - - var modules = serverPerms.ChannelPermissions[channel.Id].Modules; - - if (modules.ContainsKey(moduleName)) - modules[moduleName] = value; - else - modules.TryAdd(moduleName, value); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetChannelCommandPermission(Channel channel, string commandName, bool value) - { - var server = channel.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - if (!serverPerms.ChannelPermissions.ContainsKey(channel.Id)) - serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name)); - - var commands = serverPerms.ChannelPermissions[channel.Id].Commands; - - if (commands.ContainsKey(commandName)) - commands[commandName] = value; - else - commands.TryAdd(commandName, value); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetRoleModulePermission(Role role, string moduleName, bool value) - { - var server = role.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - if (!serverPerms.RolePermissions.ContainsKey(role.Id)) - serverPerms.RolePermissions.Add(role.Id, new Permissions(role.Name)); - - var modules = serverPerms.RolePermissions[role.Id].Modules; - - if (modules.ContainsKey(moduleName)) - modules[moduleName] = value; - else - modules.TryAdd(moduleName, value); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetRoleCommandPermission(Role role, string commandName, bool value) - { - var server = role.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - if (!serverPerms.RolePermissions.ContainsKey(role.Id)) - serverPerms.RolePermissions.Add(role.Id, new Permissions(role.Name)); - - var commands = serverPerms.RolePermissions[role.Id].Commands; - - if (commands.ContainsKey(commandName)) - commands[commandName] = value; - else - commands.TryAdd(commandName, value); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetUserModulePermission(User user, string moduleName, bool value) - { - var server = user.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - if (!serverPerms.UserPermissions.ContainsKey(user.Id)) - serverPerms.UserPermissions.Add(user.Id, new Permissions(user.Name)); - - var modules = serverPerms.UserPermissions[user.Id].Modules; - - if (modules.ContainsKey(moduleName)) - modules[moduleName] = value; - else - modules.TryAdd(moduleName, value); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetUserCommandPermission(User user, string commandName, bool value) - { - var server = user.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - if (!serverPerms.UserPermissions.ContainsKey(user.Id)) - serverPerms.UserPermissions.Add(user.Id, new Permissions(user.Name)); - - var commands = serverPerms.UserPermissions[user.Id].Commands; - - if (commands.ContainsKey(commandName)) - commands[commandName] = value; - else - commands.TryAdd(commandName, value); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetServerWordPermission(Server server, bool value) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - serverPerms.Permissions.FilterWords = value; - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetChannelWordPermission(Channel channel, bool value) - { - var server = channel.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - if (!serverPerms.ChannelPermissions.ContainsKey(channel.Id)) - serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name)); - - serverPerms.ChannelPermissions[channel.Id].FilterWords = value; - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetServerFilterInvitesPermission(Server server, bool value) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - serverPerms.Permissions.FilterInvites = value; - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetChannelFilterInvitesPermission(Channel channel, bool value) - { - var server = channel.Server; - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - - if (!serverPerms.ChannelPermissions.ContainsKey(channel.Id)) - serverPerms.ChannelPermissions.Add(channel.Id, new Permissions(channel.Name)); - - serverPerms.ChannelPermissions[channel.Id].FilterInvites = value; - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task SetCommandCooldown(Server server, string commandName, int value) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - if (value == 0) - { - int throwaway; - serverPerms.CommandCooldowns.TryRemove(commandName, out throwaway); - } - else - { - serverPerms.CommandCooldowns.AddOrUpdate(commandName, value, (str, v) => value); - } - - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - - public static async Task AddFilteredWord(Server server, string word) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - if (serverPerms.Words.Contains(word)) - throw new InvalidOperationException("That word is already banned."); - serverPerms.Words.Add(word); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - public static async Task RemoveFilteredWord(Server server, string word) - { - var serverPerms = PermissionsDict.GetOrAdd(server.Id, - new ServerPermissions(server.Id, server.Name)); - if (!serverPerms.Words.Contains(word)) - throw new InvalidOperationException("That word is not banned."); - serverPerms.Words.Remove(word); - await WriteServerToJson(serverPerms).ConfigureAwait(false); - } - } - /// - /// Holds a permission list - /// - public class Permissions - { - /// - /// Name of the parent object whose permissions these are - /// - public string Name { get; set; } - /// - /// Module name with allowed/disallowed - /// - public ConcurrentDictionary Modules { get; set; } - /// - /// Command name with allowed/disallowed - /// - public ConcurrentDictionary Commands { get; set; } - /// - /// Should the bot filter invites to other discord servers (and ref links in the future) - /// - public bool FilterInvites { get; set; } - /// - /// Should the bot filter words which are specified in the Words hashset - /// - public bool FilterWords { get; set; } - - public Permissions(string name) - { - Name = name; - Modules = new ConcurrentDictionary(); - Commands = new ConcurrentDictionary(); - FilterInvites = false; - FilterWords = false; - } - - public void CopyFrom(Permissions other) - { - Modules.Clear(); - foreach (var mp in other.Modules) - Modules.AddOrUpdate(mp.Key, mp.Value, (s, b) => mp.Value); - Commands.Clear(); - foreach (var cp in other.Commands) - Commands.AddOrUpdate(cp.Key, cp.Value, (s, b) => cp.Value); - FilterInvites = other.FilterInvites; - FilterWords = other.FilterWords; - } - - public override string ToString() - { - var toReturn = ""; - var bannedModules = Modules.Where(kvp => kvp.Value == false); - var bannedModulesArray = bannedModules as KeyValuePair[] ?? bannedModules.ToArray(); - if (bannedModulesArray.Any()) - { - toReturn += "`Banned Modules:`\n"; - toReturn = bannedModulesArray.Aggregate(toReturn, (current, m) => current + $"\t`[x] {m.Key}`\n"); - } - var bannedCommands = Commands.Where(kvp => kvp.Value == false); - var bannedCommandsArr = bannedCommands as KeyValuePair[] ?? bannedCommands.ToArray(); - if (bannedCommandsArr.Any()) - { - toReturn += "`Banned Commands:`\n"; - toReturn = bannedCommandsArr.Aggregate(toReturn, (current, c) => current + $"\t`[x] {c.Key}`\n"); - } - return toReturn; - } - } - - public class ServerPermissions - { - /// - /// The guy who can edit the permissions - /// - public string PermissionsControllerRole { get; set; } - /// - /// Does it print the error when a restriction occurs - /// - public bool Verbose { get; set; } - /// - /// The id of the thing (user/server/channel) - /// - public ulong Id { get; set; } //a string because of the role name. - /// - /// Permission object bound to the id of something/role name - /// - public Permissions Permissions { get; set; } - /// - /// Banned words, usually profanities, like word "java" - /// - public HashSet Words { get; set; } - - public Dictionary UserPermissions { get; set; } - public Dictionary ChannelPermissions { get; set; } - public Dictionary RolePermissions { get; set; } - /// - /// Dictionary of command names with their respective cooldowns - /// - public ConcurrentDictionary CommandCooldowns { get; set; } - - public ServerPermissions(ulong id, string name) - { - Id = id; - PermissionsControllerRole = "Nadeko"; - Verbose = true; - - Permissions = new Permissions(name); - Permissions.Modules.TryAdd("NSFW", false); - UserPermissions = new Dictionary(); - ChannelPermissions = new Dictionary(); - RolePermissions = new Dictionary(); - CommandCooldowns = new ConcurrentDictionary(); - Words = new HashSet(); - } - } -} \ No newline at end of file diff --git a/NadekoBot/Modules/Permissions/Classes/SimpleCheckers.cs b/NadekoBot/Modules/Permissions/Classes/SimpleCheckers.cs deleted file mode 100644 index 3ab0650d..00000000 --- a/NadekoBot/Modules/Permissions/Classes/SimpleCheckers.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.Commands.Permissions; -using System; - -namespace NadekoBot.Modules.Permissions.Classes -{ - public static class SimpleCheckers - { - public static ManageRoles CanManageRoles { get; } = new ManageRoles(); - - public static Func OwnerOnly() => - (com, user, ch) => NadekoBot.IsOwner(user.Id); - - public static Func ManageMessages() => - (com, user, ch) => user.ServerPermissions.ManageMessages; - - public static Func ManageChannels() => - (com, user, ch) => user.ServerPermissions.ManageChannels; - - public static Func ManageServer() => - (com, user, ch) => user.ServerPermissions.ManageServer; - - public class ManageRoles : IPermissionChecker - { - public bool CanRun(Command command, User user, Channel channel, out string error) - { - error = string.Empty; - if (user.ServerPermissions.ManageRoles) - return true; - error = "You do not have a permission to manage roles."; - return false; - } - } - } -} diff --git a/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs b/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs deleted file mode 100644 index 29458c7a..00000000 --- a/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Text.RegularExpressions; - -namespace NadekoBot.Modules.Permissions.Commands -{ - internal class FilterInvitesCommand : DiscordCommand - { - private readonly Regex filterRegex = new Regex(@"(?:discord(?:\.gg|app\.com\/invite)\/(?([\w]{16}|(?:[\w]+-?){3})))"); - - - public FilterInvitesCommand(DiscordModule module) : base(module) - { - NadekoBot.OnReady += () => NadekoBot.Client.MessageReceived += async (sender, args) => - { - if (args.Channel.IsPrivate || args.User.Id == NadekoBot.Client.CurrentUser.Id) return; - try - { - Classes.ServerPermissions serverPerms; - if (!IsChannelOrServerFiltering(args.Channel, out serverPerms)) return; - - if (filterRegex.IsMatch(args.Message.RawText)) - { - await args.Message.Delete().ConfigureAwait(false); - IncidentsHandler.Add(args.Server.Id, args.Channel.Id, $"User [{args.User.Name}/{args.User.Id}] posted " + - $"INVITE LINK in [{args.Channel.Name}/{args.Channel.Id}] channel.\n" + - $"`Full message:` {args.Message.Text}"); - if (serverPerms.Verbose) - await args.Channel.SendMessage($"{args.User.Mention} Invite links are not " + - $"allowed on this channel.") - .ConfigureAwait(false); - } - } - catch { } - }; - } - - private static bool IsChannelOrServerFiltering(Channel channel, out Classes.ServerPermissions serverPerms) - { - if (!PermissionsHandler.PermissionsDict.TryGetValue(channel.Server.Id, out serverPerms)) return false; - - if (serverPerms.Permissions.FilterInvites) - return true; - - Classes.Permissions perms; - return serverPerms.ChannelPermissions.TryGetValue(channel.Id, out perms) && perms.FilterInvites; - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "chnlfilterinv") - .Alias(Module.Prefix + "cfi") - .Description("Enables or disables automatic deleting of invites on the channel." + - "If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once." + - $" | `{Prefix}cfi enable #general-chat`") - .Parameter("bool") - .Parameter("channel", ParameterType.Optional) - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var chanStr = e.GetArg("channel"); - - if (chanStr?.ToLowerInvariant().Trim() != "all") - { - - var chan = string.IsNullOrWhiteSpace(chanStr) - ? e.Channel - : PermissionHelper.ValidateChannel(e.Server, chanStr); - await PermissionsHandler.SetChannelFilterInvitesPermission(chan, state).ConfigureAwait(false); - await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for **{chan.Name}** channel.") - .ConfigureAwait(false); - return; - } - //all channels - - foreach (var curChannel in e.Server.TextChannels) - { - await PermissionsHandler.SetChannelFilterInvitesPermission(curChannel, state).ConfigureAwait(false); - } - await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.") - .ConfigureAwait(false); - - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 Error: {ex.Message}") - .ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Module.Prefix + "srvrfilterinv") - .Alias(Module.Prefix + "sfi") - .Description($"Enables or disables automatic deleting of invites on the server. | `{Prefix}sfi disable`") - .Parameter("bool") - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - await PermissionsHandler.SetServerFilterInvitesPermission(e.Server, state).ConfigureAwait(false); - await e.Channel.SendMessage($"Invite Filter has been **{(state ? "enabled" : "disabled")}** for this server.") - .ConfigureAwait(false); - - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 Error: {ex.Message}").ConfigureAwait(false); - } - }); - } - } -} diff --git a/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs b/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs deleted file mode 100644 index 5b3c77e3..00000000 --- a/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs +++ /dev/null @@ -1,174 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Linq; - -namespace NadekoBot.Modules.Permissions.Commands -{ - internal class FilterWords : DiscordCommand - { - public FilterWords(DiscordModule module) : base(module) - { - NadekoBot.OnReady += () => NadekoBot.Client.MessageReceived += async (sender, args) => - { - if (args.Channel.IsPrivate || args.User.Id == NadekoBot.Client.CurrentUser.Id) return; - try - { - Classes.ServerPermissions serverPerms; - if (!IsChannelOrServerFiltering(args.Channel, out serverPerms)) return; - - var wordsInMessage = args.Message.RawText.ToLowerInvariant().Split(' '); - if (serverPerms.Words.Any(w => wordsInMessage.Contains(w))) - { - await args.Message.Delete().ConfigureAwait(false); - IncidentsHandler.Add(args.Server.Id, args.Channel.Id, $"User [{args.User.Name}/{args.User.Id}] posted " + - $"BANNED WORD in [{args.Channel.Name}/{args.Channel.Id}] channel.\n" + - $"`Full message:` {args.Message.Text}"); - if (serverPerms.Verbose) - await args.Channel.SendMessage($"{args.User.Mention} One or more of the words you used " + - $"in that sentence are not allowed here.") - .ConfigureAwait(false); - } - } - catch { } - }; - } - - private static bool IsChannelOrServerFiltering(Channel channel, out Classes.ServerPermissions serverPerms) - { - if (!PermissionsHandler.PermissionsDict.TryGetValue(channel.Server.Id, out serverPerms)) return false; - - if (serverPerms.Permissions.FilterWords) - return true; - - Classes.Permissions perms; - return serverPerms.ChannelPermissions.TryGetValue(channel.Id, out perms) && perms.FilterWords; - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "chnlfilterwords") - .Alias(Module.Prefix + "cfw") - .Description("Enables or disables automatic deleting of messages containing banned words on the channel." + - "If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once." + - $" | `{Prefix}cfw enable #general-chat`") - .Parameter("bool") - .Parameter("channel", ParameterType.Optional) - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var chanStr = e.GetArg("channel")?.ToLowerInvariant().Trim(); - - if (chanStr != "all") - { - var chan = string.IsNullOrWhiteSpace(chanStr) - ? e.Channel - : PermissionHelper.ValidateChannel(e.Server, chanStr); - await PermissionsHandler.SetChannelWordPermission(chan, state).ConfigureAwait(false); - await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **{chan.Name}** channel.").ConfigureAwait(false); - return; - } - //all channels - - foreach (var curChannel in e.Server.TextChannels) - { - await PermissionsHandler.SetChannelWordPermission(curChannel, state).ConfigureAwait(false); - } - await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** for **ALL** channels.").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 Error: {ex.Message}").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Module.Prefix + "addfilterword") - .Alias(Module.Prefix + "afw") - .Description("Adds a new word to the list of filtered words" + - $" | `{Prefix}afw poop`") - .Parameter("word", ParameterType.Unparsed) - .Do(async e => - { - try - { - var word = e.GetArg("word"); - if (string.IsNullOrWhiteSpace(word)) - return; - await PermissionsHandler.AddFilteredWord(e.Server, word.ToLowerInvariant().Trim()).ConfigureAwait(false); - await e.Channel.SendMessage($"Successfully added new filtered word.").ConfigureAwait(false); - - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 Error: {ex.Message}").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Module.Prefix + "rmvfilterword") - .Alias(Module.Prefix + "rfw") - .Description("Removes the word from the list of filtered words" + - $" | `{Prefix}rw poop`") - .Parameter("word", ParameterType.Unparsed) - .Do(async e => - { - try - { - var word = e.GetArg("word"); - if (string.IsNullOrWhiteSpace(word)) - return; - await PermissionsHandler.RemoveFilteredWord(e.Server, word.ToLowerInvariant().Trim()).ConfigureAwait(false); - await e.Channel.SendMessage($"Successfully removed filtered word.").ConfigureAwait(false); - - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 Error: {ex.Message}").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Module.Prefix + "lstfilterwords") - .Alias(Module.Prefix + "lfw") - .Description("Shows a list of filtered words" + - $" | `{Prefix}lfw`") - .Do(async e => - { - try - { - Classes.ServerPermissions serverPerms; - if (!PermissionsHandler.PermissionsDict.TryGetValue(e.Server.Id, out serverPerms)) - return; - await e.Channel.SendMessage($"There are `{serverPerms.Words.Count}` filtered words.\n" + - string.Join("\n", serverPerms.Words)).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 Error: {ex.Message}").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Module.Prefix + "srvrfilterwords") - .Alias(Module.Prefix + "sfw") - .Description($"Enables or disables automatic deleting of messages containing forbidden words on the server. | `{Prefix}sfw disable`") - .Parameter("bool") - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - await PermissionsHandler.SetServerWordPermission(e.Server, state).ConfigureAwait(false); - await e.Channel.SendMessage($"Word filtering has been **{(state ? "enabled" : "disabled")}** on this server.") - .ConfigureAwait(false); - - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 Error: {ex.Message}").ConfigureAwait(false); - } - }); - } - } -} diff --git a/NadekoBot/Modules/Permissions/PermissionsModule.cs b/NadekoBot/Modules/Permissions/PermissionsModule.cs deleted file mode 100644 index bf717094..00000000 --- a/NadekoBot/Modules/Permissions/PermissionsModule.cs +++ /dev/null @@ -1,839 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Extensions; -using NadekoBot.Modules.Games.Commands; -using NadekoBot.Modules.Permissions.Classes; -using NadekoBot.Modules.Permissions.Commands; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Permissions -{ - internal class PermissionModule : DiscordModule - { - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Permissions; - - public PermissionModule() - { - commands.Add(new FilterInvitesCommand(this)); - commands.Add(new FilterWords(this)); - } - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand(Prefix + "permrole") - .Alias(Prefix + "pr") - .Description($"Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `{Prefix}pr role`") - .Parameter("role", ParameterType.Unparsed) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("role"))) - { - await e.Channel.SendMessage($"Current permissions role is `{PermissionsHandler.GetServerPermissionsRoleName(e.Server)}`").ConfigureAwait(false); - return; - } - - var arg = e.GetArg("role"); - Discord.Role role = null; - try - { - role = PermissionHelper.ValidateRole(e.Server, arg); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - await e.Channel.SendMessage($"Role `{arg}` probably doesn't exist. Create the role with that name first.").ConfigureAwait(false); - return; - } - 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); - }); - - cgb.CreateCommand(Prefix + "rolepermscopy") - .Alias(Prefix + "rpc") - .Description($"Copies BOT PERMISSIONS (not discord permissions) from one role to another. |`{Prefix}rpc Some Role ~ Some other role`") - .Parameter("from_to", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("from_to")?.Trim(); - if (string.IsNullOrWhiteSpace(arg) || !arg.Contains('~')) - return; - var args = arg.Split('~').Select(a => a.Trim()).ToArray(); - if (args.Length > 2) - { - await e.Channel.SendMessage("💢Invalid number of '~'s in the argument.").ConfigureAwait(false); - return; - } - try - { - var fromRole = PermissionHelper.ValidateRole(e.Server, args[0]); - var toRole = PermissionHelper.ValidateRole(e.Server, args[1]); - - await PermissionsHandler.CopyRolePermissions(fromRole, toRole).ConfigureAwait(false); - await e.Channel.SendMessage($"Copied permission settings from **{fromRole.Name}** to **{toRole.Name}**.").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢{ex.Message}").ConfigureAwait(false); - } - }); - cgb.CreateCommand(Prefix + "chnlpermscopy") - .Alias(Prefix + "cpc") - .Description($"Copies BOT PERMISSIONS (not discord permissions) from one channel to another. |`{Prefix}cpc Some Channel ~ Some other channel`") - .Parameter("from_to", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("from_to")?.Trim(); - if (string.IsNullOrWhiteSpace(arg) || !arg.Contains('~')) - return; - var args = arg.Split('~').Select(a => a.Trim()).ToArray(); - if (args.Length > 2) - { - await e.Channel.SendMessage("💢Invalid number of '~'s in the argument."); - return; - } - try - { - var fromChannel = PermissionHelper.ValidateChannel(e.Server, args[0]); - var toChannel = PermissionHelper.ValidateChannel(e.Server, args[1]); - - await PermissionsHandler.CopyChannelPermissions(fromChannel, toChannel).ConfigureAwait(false); - await e.Channel.SendMessage($"Copied permission settings from **{fromChannel.Name}** to **{toChannel.Name}**.").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢{ex.Message}"); - } - }); - cgb.CreateCommand(Prefix + "usrpermscopy") - .Alias(Prefix + "upc") - .Description($"Copies BOT PERMISSIONS (not discord permissions) from one role to another. |`{Prefix}upc @SomeUser ~ @SomeOtherUser`") - .Parameter("from_to", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("from_to")?.Trim(); - if (string.IsNullOrWhiteSpace(arg) || !arg.Contains('~')) - return; - var args = arg.Split('~').Select(a => a.Trim()).ToArray(); - if (args.Length > 2) - { - await e.Channel.SendMessage("💢Invalid number of '~'s in the argument.").ConfigureAwait(false); - return; - } - try - { - var fromUser = PermissionHelper.ValidateUser(e.Server, args[0]); - var toUser = PermissionHelper.ValidateUser(e.Server, args[1]); - - await PermissionsHandler.CopyUserPermissions(fromUser, toUser).ConfigureAwait(false); - await e.Channel.SendMessage($"Copied permission settings from **{fromUser.ToString()}**to * *{toUser.ToString()}**.").ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢{ex.Message}"); - } - }); - - cgb.CreateCommand(Prefix + "verbose") - .Alias(Prefix + "v") - .Description($"Sets whether to show when a command/module is blocked. | `{Prefix}verbose true`") - .Parameter("arg", ParameterType.Required) - .Do(async e => - { - var arg = e.GetArg("arg"); - var val = PermissionHelper.ValidateBool(arg); - await PermissionsHandler.SetVerbosity(e.Server, val).ConfigureAwait(false); - await e.Channel.SendMessage($"Verbosity set to {val}.").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "srvrperms") - .Alias(Prefix + "sp") - .Description($"Shows banned permissions for this server. | `{Prefix}sp`") - .Do(async e => - { - var perms = PermissionsHandler.GetServerPermissions(e.Server); - if (string.IsNullOrWhiteSpace(perms?.ToString())) - await e.Channel.SendMessage("No permissions set for this server.").ConfigureAwait(false); - await e.Channel.SendMessage(perms.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "roleperms") - .Alias(Prefix + "rp") - .Description($"Shows banned permissions for a certain role. No argument means for everyone. | `{Prefix}rp AwesomeRole`") - .Parameter("role", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("role"); - var role = e.Server.EveryoneRole; - if (!string.IsNullOrWhiteSpace(arg)) - try - { - role = PermissionHelper.ValidateRole(e.Server, arg); - } - catch (Exception ex) - { - await e.Channel.SendMessage("💢 Error: " + ex.Message).ConfigureAwait(false); - return; - } - - var perms = PermissionsHandler.GetRolePermissionsById(e.Server, role.Id); - - if (string.IsNullOrWhiteSpace(perms?.ToString())) - await e.Channel.SendMessage($"No permissions set for **{role.Name}** role.").ConfigureAwait(false); - await e.Channel.SendMessage(perms.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "chnlperms") - .Alias(Prefix + "cp") - .Description($"Shows banned permissions for a certain channel. No argument means for this channel. | `{Prefix}cp #dev`") - .Parameter("channel", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("channel"); - var channel = e.Channel; - if (!string.IsNullOrWhiteSpace(arg)) - try - { - channel = PermissionHelper.ValidateChannel(e.Server, arg); - } - catch (Exception ex) - { - await e.Channel.SendMessage("💢 Error: " + ex.Message).ConfigureAwait(false); - return; - } - - var perms = PermissionsHandler.GetChannelPermissionsById(e.Server, channel.Id); - if (string.IsNullOrWhiteSpace(perms?.ToString())) - await e.Channel.SendMessage($"No permissions set for **{channel.Name}** channel.").ConfigureAwait(false); - await e.Channel.SendMessage(perms.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "userperms") - .Alias(Prefix + "up") - .Description($"Shows banned permissions for a certain user. No argument means for yourself. | `{Prefix}up Kwoth`") - .Parameter("user", ParameterType.Unparsed) - .Do(async e => - { - var user = e.User; - if (!string.IsNullOrWhiteSpace(e.GetArg("user"))) - try - { - user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user")); - } - catch (Exception ex) - { - await e.Channel.SendMessage("💢 Error: " + ex.Message).ConfigureAwait(false); - return; - } - - var perms = PermissionsHandler.GetUserPermissionsById(e.Server, user.Id); - if (string.IsNullOrWhiteSpace(perms?.ToString())) - await e.Channel.SendMessage($"No permissions set for user **{user.Name}**.").ConfigureAwait(false); - await e.Channel.SendMessage(perms.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "srvrmdl") - .Alias(Prefix + "sm") - .Parameter("module", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Description($"Sets a module's permission at the server level. | `{Prefix}sm \"module name\" enable`") - .Do(async e => - { - try - { - var module = PermissionHelper.ValidateModule(e.GetArg("module")); - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "srvrcmd").Alias(Prefix + "sc") - .Parameter("command", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Description($"Sets a command's permission at the server level. | `{Prefix}sc \"command name\" disable`") - .Do(async e => - { - try - { - var command = PermissionHelper.ValidateCommand(e.GetArg("command")); - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "rolemdl").Alias(Prefix + "rm") - .Parameter("module", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Parameter("role", ParameterType.Unparsed) - .Description($"Sets a module's permission at the role level. | `{Prefix}rm \"module name\" enable MyRole`") - .Do(async e => - { - try - { - var module = PermissionHelper.ValidateModule(e.GetArg("module")); - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - - if (e.GetArg("role")?.ToLower() == "all") - { - foreach (var role in e.Server.Roles) - { - await PermissionsHandler.SetRoleModulePermission(role, module, state).ConfigureAwait(false); - } - await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **ALL** roles.").ConfigureAwait(false); - } - else - { - var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); - - 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); - } - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "rolecmd").Alias(Prefix + "rc") - .Parameter("command", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Parameter("role", ParameterType.Unparsed) - .Description($"Sets a command's permission at the role level. | `{Prefix}rc \"command name\" disable MyRole`") - .Do(async e => - { - try - { - var command = PermissionHelper.ValidateCommand(e.GetArg("command")); - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - - if (e.GetArg("role")?.ToLower() == "all") - { - foreach (var role in e.Server.Roles) - { - await PermissionsHandler.SetRoleCommandPermission(role, command, state).ConfigureAwait(false); - } - await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** for **ALL** roles.").ConfigureAwait(false); - } - else - { - var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); - - 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); - } - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "chnlmdl").Alias(Prefix + "cm") - .Parameter("module", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Parameter("channel", ParameterType.Unparsed) - .Description($"Sets a module's permission at the channel level. | `{Prefix}cm \"module name\" enable SomeChannel`") - .Do(async e => - { - try - { - var module = PermissionHelper.ValidateModule(e.GetArg("module")); - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var channelArg = e.GetArg("channel"); - if (channelArg?.ToLower() == "all") - { - foreach (var channel in e.Server.TextChannels) - { - await PermissionsHandler.SetChannelModulePermission(channel, module, state).ConfigureAwait(false); - } - await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false); - } - else if (string.IsNullOrWhiteSpace(channelArg)) - { - 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); - } - else - { - var channel = PermissionHelper.ValidateChannel(e.Server, channelArg); - - 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); - } - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "chnlcmd").Alias(Prefix + "cc") - .Parameter("command", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Parameter("channel", ParameterType.Unparsed) - .Description($"Sets a command's permission at the channel level. | `{Prefix}cc \"command name\" enable SomeChannel`") - .Do(async e => - { - try - { - var command = PermissionHelper.ValidateCommand(e.GetArg("command")); - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - - if (e.GetArg("channel")?.ToLower() == "all") - { - foreach (var channel in e.Server.TextChannels) - { - await PermissionsHandler.SetChannelCommandPermission(channel, command, state).ConfigureAwait(false); - } - await e.Channel.SendMessage($"Command **{command}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false); - } - else - { - var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel")); - - 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); - } - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "usrmdl").Alias(Prefix + "um") - .Parameter("module", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Parameter("user", ParameterType.Unparsed) - .Description($"Sets a module's permission at the user level. | `{Prefix}um \"module name\" enable SomeUsername`") - .Do(async e => - { - try - { - var module = PermissionHelper.ValidateModule(e.GetArg("module")); - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user")); - - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "usrcmd").Alias(Prefix + "uc") - .Parameter("command", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Parameter("user", ParameterType.Unparsed) - .Description($"Sets a command's permission at the user level. | `{Prefix}uc \"command name\" enable SomeUsername`") - .Do(async e => - { - try - { - var command = PermissionHelper.ValidateCommand(e.GetArg("command")); - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var user = PermissionHelper.ValidateUser(e.Server, e.GetArg("user")); - - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "allsrvrmdls").Alias(Prefix + "asm") - .Parameter("bool", ParameterType.Required) - .Description($"Sets permissions for all modules at the server level. | `{Prefix}asm [enable/disable]`") - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - - foreach (var module in NadekoBot.Client.GetService().Modules) - { - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "allsrvrcmds").Alias(Prefix + "asc") - .Parameter("module", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Description($"Sets permissions for all commands from a certain module at the server level. | `{Prefix}asc \"module name\" [enable/disable]`") - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var module = PermissionHelper.ValidateModule(e.GetArg("module")); - - foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) - { - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "allchnlmdls").Alias(Prefix + "acm") - .Parameter("bool", ParameterType.Required) - .Parameter("channel", ParameterType.Unparsed) - .Description($"Sets permissions for all modules at the channel level. | `{Prefix}acm [enable/disable] SomeChannel`") - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var chArg = e.GetArg("channel"); - var channel = string.IsNullOrWhiteSpace(chArg) ? e.Channel : PermissionHelper.ValidateChannel(e.Server, chArg); - foreach (var module in NadekoBot.Client.GetService().Modules) - { - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "allchnlcmds").Alias(Prefix + "acc") - .Parameter("module", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Parameter("channel", ParameterType.Unparsed) - .Description($"Sets permissions for all commands from a certain module at the channel level. | `{Prefix}acc \"module name\" [enable/disable] SomeChannel`") - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var module = PermissionHelper.ValidateModule(e.GetArg("module")); - var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel")); - foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) - { - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "allrolemdls").Alias(Prefix + "arm") - .Parameter("bool", ParameterType.Required) - .Parameter("role", ParameterType.Unparsed) - .Description($"Sets permissions for all modules at the role level. | `{Prefix}arm [enable/disable] MyRole`") - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); - foreach (var module in NadekoBot.Client.GetService().Modules) - { - 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); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "allrolecmds").Alias(Prefix + "arc") - .Parameter("module", ParameterType.Required) - .Parameter("bool", ParameterType.Required) - .Parameter("role", ParameterType.Unparsed) - .Description($"Sets permissions for all commands from a certain module at the role level. | `{Prefix}arc \"module name\" [enable/disable] MyRole`") - .Do(async e => - { - try - { - var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var module = PermissionHelper.ValidateModule(e.GetArg("module")); - if (e.GetArg("role")?.ToLower() == "all") - { - foreach (var role in e.Server.Roles) - { - foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) - { - 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); - } - else - { - var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); - - foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) - { - 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); - } - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "ubl") - .Description($"Blacklists a mentioned user. **Bot Owner Only!**| `{Prefix}ubl [user_mention]`") - .Parameter("user", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - await Task.Run(async () => - { - if (!e.Message.MentionedUsers.Any()) return; - var usr = e.Message.MentionedUsers.First(); - NadekoBot.Config.UserBlacklist.Add(usr.Id); - await ConfigHandler.SaveConfig().ConfigureAwait(false); - await e.Channel.SendMessage($"`Sucessfully blacklisted user {usr.Name}`").ConfigureAwait(false); - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "uubl") - .Description($"Unblacklists a mentioned user. **Bot Owner Only!** | `{Prefix}uubl [user_mention]`") - .Parameter("user", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - await Task.Run(async () => - { - if (!e.Message.MentionedUsers.Any()) return; - var usr = e.Message.MentionedUsers.First(); - if (NadekoBot.Config.UserBlacklist.Contains(usr.Id)) - { - NadekoBot.Config.UserBlacklist.Remove(usr.Id); - await ConfigHandler.SaveConfig().ConfigureAwait(false); - await e.Channel.SendMessage($"`Sucessfully unblacklisted user {usr.Name}`").ConfigureAwait(false); - } - else - { - await e.Channel.SendMessage($"`{usr.Name} was not in blacklist`").ConfigureAwait(false); - } - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "cbl") - .Description($"Blacklists a mentioned channel (#general for example). | `{Prefix}cbl #some_channel`") - .Parameter("channel", ParameterType.Unparsed) - .Do(async e => - { - await Task.Run(async () => - { - if (!e.Message.MentionedChannels.Any()) return; - var ch = e.Message.MentionedChannels.First(); - NadekoBot.Config.ChannelBlacklist.Add(ch.Id); - await ConfigHandler.SaveConfig().ConfigureAwait(false); - await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false); - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "cubl") - .Description($"Unblacklists a mentioned channel (#general for example). | `{Prefix}cubl #some_channel`") - .Parameter("channel", ParameterType.Unparsed) - .Do(async e => - { - await Task.Run(async () => - { - if (!e.Message.MentionedChannels.Any()) return; - var ch = e.Message.MentionedChannels.First(); - if (NadekoBot.Config.ChannelBlacklist.Contains(ch.Id)) - { - NadekoBot.Config.ChannelBlacklist.Remove(ch.Id); - await ConfigHandler.SaveConfig().ConfigureAwait(false); - await e.Channel.SendMessage($"`Sucessfully unblacklisted channel {ch.Name}`").ConfigureAwait(false); - } - else - await e.Channel.SendMessage($"`{ch.Name} was not in blacklist`").ConfigureAwait(false); - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "sbl") - .Description($"Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | `{Prefix}sbl [servername/serverid]`") - .Parameter("server", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - await Task.Run(async () => - { - var arg = e.GetArg("server")?.Trim(); - if (string.IsNullOrWhiteSpace(arg)) - return; - var server = NadekoBot.Client.Servers.FirstOrDefault(s => s.Id.ToString() == arg) ?? - NadekoBot.Client.FindServers(arg.Trim()).FirstOrDefault(); - if (server == null) - { - await e.Channel.SendMessage("Cannot find that server").ConfigureAwait(false); - return; - } - var serverId = server.Id; - NadekoBot.Config.ServerBlacklist.Add(serverId); - await ConfigHandler.SaveConfig().ConfigureAwait(false); - //cleanup trivias and typeracing - Modules.Games.Commands.Trivia.TriviaGame trivia; - TriviaCommands.RunningTrivias.TryRemove(serverId, out trivia); - TypingGame typeracer; - SpeedTyping.RunningContests.TryRemove(serverId, out typeracer); - - await e.Channel.SendMessage($"`Sucessfully blacklisted server {server.Name}`").ConfigureAwait(false); - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "cmdcooldown") - .Alias(Prefix+ "cmdcd") - .Description($"Sets a cooldown per user for a command. Set 0 to clear. **Needs Manager Messages Permissions**| `{Prefix}cmdcd \"some cmd\" 5`") - .Parameter("command", ParameterType.Required) - .Parameter("secs",ParameterType.Required) - .AddCheck(SimpleCheckers.ManageMessages()) - .Do(async e => - { - try - { - var command = PermissionHelper.ValidateCommand(e.GetArg("command")); - var secsStr = e.GetArg("secs").Trim(); - int secs; - if (!int.TryParse(secsStr, out secs) || secs < 0 || secs > 3600) - throw new ArgumentOutOfRangeException("secs", "Invalid second parameter. (Must be a number between 0 and 3600)"); - - - await PermissionsHandler.SetCommandCooldown(e.Server, command, secs).ConfigureAwait(false); - if(secs == 0) - await e.Channel.SendMessage($"Command **{command}** has no coooldown now.").ConfigureAwait(false); - else - await e.Channel.SendMessage($"Command **{command}** now has a **{secs} {(secs==1 ? "second" : "seconds")}** cooldown.").ConfigureAwait(false); - } - catch (ArgumentException exArg) - { - await e.Channel.SendMessage(exArg.Message).ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage("Something went terribly wrong - " + ex.Message).ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "allcmdcooldowns") - .Alias(Prefix + "acmdcds") - .Description($"Shows a list of all commands and their respective cooldowns. | `{Prefix}acmdcds`") - .Do(async e => - { - ServerPermissions perms; - PermissionsHandler.PermissionsDict.TryGetValue(e.Server.Id, out perms); - if (perms == null) - return; - - if (!perms.CommandCooldowns.Any()) - { - await e.Channel.SendMessage("`No command cooldowns set.`").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage(SearchHelper.ShowInPrettyCode(perms.CommandCooldowns.Select(c=>c.Key+ ": "+c.Value+" secs"),s=>$"{s,-30}",2)).ConfigureAwait(false); - }); - }); - } - } -} diff --git a/NadekoBot/Modules/Pokemon/PokeStats.cs b/NadekoBot/Modules/Pokemon/PokeStats.cs deleted file mode 100644 index b9795043..00000000 --- a/NadekoBot/Modules/Pokemon/PokeStats.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace NadekoBot.Modules.Pokemon -{ - class PokeStats - { - //Health left - public int Hp { get; set; } = 500; - public int MaxHp { get; } = 500; - //Amount of moves made since last time attacked - public int MovesMade { get; set; } = 0; - //Last people attacked - public List LastAttacked { get; set; } = new List(); - } -} diff --git a/NadekoBot/Modules/Pokemon/PokemonModule.cs b/NadekoBot/Modules/Pokemon/PokemonModule.cs deleted file mode 100644 index d494fffa..00000000 --- a/NadekoBot/Modules/Pokemon/PokemonModule.cs +++ /dev/null @@ -1,339 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.Classes.JSONModels; -using NadekoBot.DataModels; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace NadekoBot.Modules.Pokemon -{ - class PokemonModule : DiscordModule - { - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Pokemon; - - private ConcurrentDictionary Stats = new ConcurrentDictionary(); - - public PokemonModule() - { - - } - - private int GetDamage(PokemonType usertype, PokemonType targetType) - { - var rng = new Random(); - int damage = rng.Next(40, 60); - foreach (PokemonMultiplier Multiplier in usertype.Multipliers) - { - if (Multiplier.Type == targetType.Name) - { - var multiplier = Multiplier.Multiplication; - damage = (int)(damage * multiplier); - } - } - - return damage; - } - - private PokemonType GetPokeType(ulong id) - { - - var db = DbHandler.Instance.GetAllRows(); - Dictionary setTypes = db.ToDictionary(x => x.UserId, y => y.type); - if (setTypes.ContainsKey((long)id)) - { - return stringToPokemonType(setTypes[(long)id]); - } - int count = NadekoBot.Config.PokemonTypes.Count; - - int remainder = Math.Abs((int)(id % (ulong)count)); - - return NadekoBot.Config.PokemonTypes[remainder]; - } - - - - private PokemonType stringToPokemonType(string v) - { - var str = v.ToUpperInvariant(); - var list = NadekoBot.Config.PokemonTypes; - foreach (PokemonType p in list) - { - if (str == p.Name) - { - return p; - } - } - return null; - } - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - cgb.AddCheck(PermissionChecker.Instance); - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand(Prefix + "attack") - .Description($"Attacks a target with the given move. Use `{Prefix}movelist` to see a list of moves your type can use. | `{Prefix}attack \"vine whip\" @someguy`") - .Parameter("move", ParameterType.Required) - .Parameter("target", ParameterType.Unparsed) - .Do(async e => - { - var move = e.GetArg("move"); - var targetStr = e.GetArg("target")?.Trim(); - if (string.IsNullOrWhiteSpace(targetStr)) - return; - var target = e.Server.FindUsers(targetStr).FirstOrDefault(); - if (target == null) - { - await e.Channel.SendMessage("No such person.").ConfigureAwait(false); - return; - } - else if (target == e.User) - { - await e.Channel.SendMessage("You can't attack yourself.").ConfigureAwait(false); - return; - } - // Checking stats first, then move - //Set up the userstats - PokeStats userStats; - userStats = Stats.GetOrAdd(e.User.Id, new PokeStats()); - - //Check if able to move - //User not able if HP < 0, has made more than 4 attacks - if (userStats.Hp < 0) - { - await e.Channel.SendMessage($"{e.User.Mention} has fainted and was not able to move!").ConfigureAwait(false); - return; - } - if (userStats.MovesMade >= 5) - { - await e.Channel.SendMessage($"{e.User.Mention} has used too many moves in a row and was not able to move!").ConfigureAwait(false); - return; - } - if (userStats.LastAttacked.Contains(target.Id)) - { - await e.Channel.SendMessage($"{e.User.Mention} can't attack again without retaliation!").ConfigureAwait(false); - return; - } - //get target stats - PokeStats targetStats; - targetStats = Stats.GetOrAdd(target.Id, new PokeStats()); - - //If target's HP is below 0, no use attacking - if (targetStats.Hp <= 0) - { - await e.Channel.SendMessage($"{target.Mention} has already fainted!").ConfigureAwait(false); - return; - } - - //Check whether move can be used - PokemonType userType = GetPokeType(e.User.Id); - - var enabledMoves = userType.Moves; - if (!enabledMoves.Contains(move.ToLowerInvariant())) - { - await e.Channel.SendMessage($"{e.User.Mention} was not able to use **{move}**, use `{Prefix}ml` to see moves you can use").ConfigureAwait(false); - return; - } - - //get target type - PokemonType targetType = GetPokeType(target.Id); - //generate damage - int damage = GetDamage(userType, targetType); - //apply damage to target - targetStats.Hp -= damage; - - var response = $"{e.User.Mention} used **{move}**{userType.Icon} on {target.Mention}{targetType.Icon} for **{damage}** damage"; - - //Damage type - if (damage < 40) - { - response += "\nIt's not effective.."; - } - else if (damage > 60) - { - response += "\nIt's super effective!"; - } - else - { - response += "\nIt's somewhat effective"; - } - - //check fainted - - if (targetStats.Hp <= 0) - { - response += $"\n**{target.Name}** has fainted!"; - } - else - { - response += $"\n**{target.Name}** has {targetStats.Hp} HP remaining"; - } - - //update other stats - userStats.LastAttacked.Add(target.Id); - userStats.MovesMade++; - targetStats.MovesMade = 0; - if (targetStats.LastAttacked.Contains(e.User.Id)) - { - targetStats.LastAttacked.Remove(e.User.Id); - } - - //update dictionary - //This can stay the same right? - Stats[e.User.Id] = userStats; - Stats[target.Id] = targetStats; - - await e.Channel.SendMessage(response).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "movelist") - .Alias(Prefix + "ml") - .Description($"Lists the moves you are able to use | `{Prefix}ml`") - .Do(async e => - { - var userType = GetPokeType(e.User.Id); - var movesList = userType.Moves; - var str = $"**Moves for `{userType.Name}` type.**"; - foreach (string m in movesList) - { - str += $"\n{userType.Icon}{m}"; - } - await e.Channel.SendMessage(str).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "heal") - .Description($"Heals someone. Revives those who fainted. Costs a {NadekoBot.Config.CurrencyName} | `{Prefix}heal @someone`") - .Parameter("target", ParameterType.Unparsed) - .Do(async e => - { - var targetStr = e.GetArg("target")?.Trim(); - if (string.IsNullOrWhiteSpace(targetStr)) - return; - var usr = e.Server.FindUsers(targetStr).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("No such person.").ConfigureAwait(false); - return; - } - if (Stats.ContainsKey(usr.Id)) - { - - var targetStats = Stats[usr.Id]; - int HP = targetStats.Hp; - if (targetStats.Hp == targetStats.MaxHp) - { - await e.Channel.SendMessage($"{usr.Name} already has full HP!").ConfigureAwait(false); - return; - } - //Payment~ - var amount = 1; - var pts = Classes.DbHandler.Instance.GetStateByUserId((long)e.User.Id)?.Value ?? 0; - if (pts < amount) - { - await e.Channel.SendMessage($"{e.User.Mention} you don't have enough {NadekoBot.Config.CurrencyName}s! \nYou still need {amount - pts} {NadekoBot.Config.CurrencySign} to be able to do this!").ConfigureAwait(false); - return; - } - var target = (usr.Id == e.User.Id) ? "yourself" : usr.Name; - await FlowersHandler.RemoveFlowers(e.User, $"Poke-Heal {target}", amount).ConfigureAwait(false); - //healing - targetStats.Hp = targetStats.MaxHp; - if (HP < 0) - { - //Could heal only for half HP? - Stats[usr.Id].Hp = (targetStats.MaxHp / 2); - await e.Channel.SendMessage($"{e.User.Name} revived {usr.Name} with one {NadekoBot.Config.CurrencySign}").ConfigureAwait(false); - return; - } - var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(NadekoBot.Config.CurrencyName[0]); - await e.Channel.SendMessage($"{e.User.Name} healed {usr.Name} for {targetStats.MaxHp - HP} HP with {(vowelFirst ? "an" : "a")} {NadekoBot.Config.CurrencySign}").ConfigureAwait(false); - return; - } - else - { - await e.Channel.SendMessage($"{usr.Name} already has full HP!").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "type") - .Description($"Get the poketype of the target. | `{Prefix}type @someone`") - .Parameter("target", ParameterType.Unparsed) - .Do(async e => - { - var usrStr = e.GetArg("target")?.Trim(); - if (string.IsNullOrWhiteSpace(usrStr)) - return; - var usr = e.Server.FindUsers(usrStr).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("No such person.").ConfigureAwait(false); - return; - } - var pType = GetPokeType(usr.Id); - await e.Channel.SendMessage($"Type of {usr.Name} is **{pType.Name.ToLowerInvariant()}**{pType.Icon}").ConfigureAwait(false); - - }); - - cgb.CreateCommand(Prefix + "settype") - .Description($"Set your poketype. Costs a {NadekoBot.Config.CurrencyName}. | `{Prefix}settype fire`") - .Parameter("targetType", ParameterType.Unparsed) - .Do(async e => - { - var targetTypeStr = e.GetArg("targetType")?.ToUpperInvariant(); - if (string.IsNullOrWhiteSpace(targetTypeStr)) - return; - var targetType = stringToPokemonType(targetTypeStr); - if (targetType == null) - { - await e.Channel.SendMessage("Invalid type specified. Type must be one of:\n" + string.Join(", ", NadekoBot.Config.PokemonTypes.Select(t => t.Name.ToUpperInvariant()))).ConfigureAwait(false); - return; - } - if (targetType == GetPokeType(e.User.Id)) - { - await e.Channel.SendMessage($"Your type is already {targetType.Name.ToLowerInvariant()}{targetType.Icon}").ConfigureAwait(false); - return; - } - - //Payment~ - var amount = 1; - var pts = DbHandler.Instance.GetStateByUserId((long)e.User.Id)?.Value ?? 0; - if (pts < amount) - { - await e.Channel.SendMessage($"{e.User.Mention} you don't have enough {NadekoBot.Config.CurrencyName}s! \nYou still need {amount - pts} {NadekoBot.Config.CurrencySign} to be able to do this!").ConfigureAwait(false); - return; - } - await FlowersHandler.RemoveFlowers(e.User, $"set usertype to {targetTypeStr}", amount).ConfigureAwait(false); - //Actually changing the type here - var preTypes = DbHandler.Instance.GetAllRows(); - Dictionary Dict = preTypes.ToDictionary(x => x.UserId, y => y.Id.Value); - if (Dict.ContainsKey((long)e.User.Id)) - { - //delete previous type - DbHandler.Instance.Delete(Dict[(long)e.User.Id]); - } - - DbHandler.Instance.Connection.Insert(new UserPokeTypes - { - UserId = (long)e.User.Id, - type = targetType.Name - }, typeof(UserPokeTypes)); - - //Now for the response - - await e.Channel.SendMessage($"Set type of {e.User.Mention} to {targetTypeStr}{targetType.Icon} for a {NadekoBot.Config.CurrencySign}").ConfigureAwait(false); - }); - }); - } - } -} - - - - diff --git a/NadekoBot/Modules/Programming/Commands/HaskellRepl.cs b/NadekoBot/Modules/Programming/Commands/HaskellRepl.cs deleted file mode 100644 index f8c1f78d..00000000 --- a/NadekoBot/Modules/Programming/Commands/HaskellRepl.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -/// -/// I have no idea what am i doing -/// -namespace NadekoBot.Modules.Programming.Commands -{ - class HaskellRepl : DiscordCommand - { - ConcurrentQueue> commandQueue = new ConcurrentQueue>(); - - Thread haskellThread; - - public HaskellRepl(DiscordModule module) : base(module) - { - //start haskell interpreter - - haskellThread = new Thread(new ThreadStart(() => - { - var p = Process.Start(new ProcessStartInfo - { - FileName = "stack", //shouldn't use repl, but a Language.Haskell.Interpreter somehow - Arguments = "repl", - UseShellExecute = false, - RedirectStandardInput = true, - RedirectStandardOutput = true, - CreateNoWindow = true, - }); - - Task.Run(async () => - { - while (true) - { - while (commandQueue.Count == 0) - await Task.Delay(100); - - //read from queue - KeyValuePair com; - if (!commandQueue.TryDequeue(out com)) - { - await Task.Delay(100); - continue; - } - //var bytes = Encoding.ASCII.GetBytes(com.Key); - - //send the command to the process - p.StandardInput.WriteLine(com.Key); - - //wait 50 ms for execution - await Task.Delay(50); - - //read everything from the output - var outBuffer = new byte[1500]; - - p.StandardOutput.BaseStream.Read(outBuffer, 0, 1500); - - var outStr = Encoding.ASCII.GetString(outBuffer); - //send to channel - await com.Value.SendMessage($"```hs\nPrelude> {com.Key}\n" + outStr + "\n```"); - } - }); - - })); - haskellThread.Start(); - - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "hs") - .Description("Executes a haskell express with LAMBDABOT") - .Parameter("command", ParameterType.Unparsed) - .Do(e => - { - var com = e.GetArg("command")?.Trim(); - if (string.IsNullOrWhiteSpace(com)) - return; - - //send a command and a channel to the queue - commandQueue.Enqueue(new KeyValuePair(com, e.Channel)); - }); - } - } -} diff --git a/NadekoBot/Modules/Programming/ProgrammingModule.cs b/NadekoBot/Modules/Programming/ProgrammingModule.cs deleted file mode 100644 index feb3c4e2..00000000 --- a/NadekoBot/Modules/Programming/ProgrammingModule.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Discord.Modules; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using NadekoBot.Modules.Programming.Commands; - -namespace NadekoBot.Modules.Programming -{ - class ProgrammingModule : DiscordModule - { - public override string Prefix => NadekoBot.Config.CommandPrefixes.Programming; - - public ProgrammingModule() - { - commands.Add(new HaskellRepl(this)); - } - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - cgb.AddCheck(PermissionChecker.Instance); - commands.ForEach(c => c.Init(cgb)); - }); - } - } -} diff --git a/NadekoBot/Modules/Searches/Commands/ConverterCommand.cs b/NadekoBot/Modules/Searches/Commands/ConverterCommand.cs deleted file mode 100644 index ada8709a..00000000 --- a/NadekoBot/Modules/Searches/Commands/ConverterCommand.cs +++ /dev/null @@ -1,171 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using ScaredFingers.UnitsConversion; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Searches.Commands -{ - class ConverterCommand : DiscordCommand - { - - public ConverterCommand(DiscordModule module) : base(module) - { - if (unitTables == null) - { - CultureInfo ci = new CultureInfo("en-US"); - Thread.CurrentThread.CurrentCulture = ci; - unitTables = new List(); - unitTables.Add(UnitTable.LengthTable); - unitTables.Add(UnitTable.TemperatureTable); - unitTables.Add(UnitTable.VolumeTable); - unitTables.Add(UnitTable.WeightTable); - reInitCurrencyConverterTable(); - } - - } - - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "convert") - .Description($"Convert quantities from>to. | `{Prefix}convert m>km 1000`") - .Parameter("from-to", ParameterType.Required) - .Parameter("quantity", ParameterType.Optional) - .Do(ConvertFunc()); - cgb.CreateCommand(Module.Prefix + "convertlist") - .Description("List of the convertable dimensions and currencies.") - .Do(ConvertListFunc()); - } - - private Func ConvertListFunc() => - async e => - { - reInitCurrencyConverterTable(); - string msg = ""; - foreach (var tmpTable in unitTables) - { - int i = 1; - while (tmpTable.IsKnownUnit(i)) - { - msg += tmpTable.GetUnitName(i) + " (" + tmpTable.GetUnitSymbol(i) + "); "; - i++; - } - msg += "\n"; - } - foreach (var curr in exchangeRateProvider.Currencies) - { - msg += curr + "; "; - } - - await e.Channel.SendMessage(msg).ConfigureAwait(false); - }; - - private Func ConvertFunc() => - async e => - { - try - { - await e.Channel.SendIsTyping().ConfigureAwait(false); - - string from = e.GetArg("from-to").ToLowerInvariant().Split('>')[0]; - string to = e.GetArg("from-to").ToLowerInvariant().Split('>')[1]; - - float quantity = 1.0f; - if (!float.TryParse(e.GetArg("quantity"), out quantity)) - { - quantity = 1.0f; - } - - int fromCode, toCode = 0; - UnitTable table = null; - ResolveUnitCodes(from, to, out table, out fromCode, out toCode); - - if (table != null) - { - Unit inUnit = new Unit(fromCode, quantity, table); - Unit outUnit = inUnit.Convert(toCode); - await e.Channel.SendMessage(inUnit.ToString() + " = " + outUnit.ToString()).ConfigureAwait(false); - } - else - { - CultureInfo ci = new CultureInfo("en-US"); - Thread.CurrentThread.CurrentCulture = ci; - reInitCurrencyConverterTable(); - Unit inUnit = currTable.CreateUnit(quantity, from.ToUpperInvariant()); - Unit outUnit = inUnit.Convert(currTable.CurrencyCode(to.ToUpperInvariant())); - await e.Channel.SendMessage(inUnit.ToString() + " = " + outUnit.ToString()).ConfigureAwait(false); - } - } - catch //(Exception ex) - { - //Console.WriteLine(ex.ToString()); - await e.Channel.SendMessage("Bad input format, or sth went wrong... Try to list them with `" + Module.Prefix + "`convertlist").ConfigureAwait(false); - } - }; - - private void reInitCurrencyConverterTable() - { - if (lastChanged == null || lastChanged.DayOfYear != DateTime.Now.DayOfYear) - { - try - { - exchangeRateProvider = new WebExchangeRatesProvider(); - currTable = new CurrencyExchangeTable(exchangeRateProvider); - lastChanged = DateTime.Now; - } - catch - { - Console.WriteLine("Error with the currency download."); - } - } - } - - private void ResolveUnitCodes(string from, string to, out UnitTable table, out int fromCode, out int toCode) - { - foreach (var tmpTable in unitTables) - { - int f = LookupUnit(tmpTable, from); - int t = LookupUnit(tmpTable, to); - if (f > 0 && t > 0) - { - table = tmpTable; - fromCode = f; - toCode = t; - return; - } - } - table = null; - fromCode = 0; - toCode = 0; - } - - private int LookupUnit(UnitTable table, string lookup) - { - string wellformedLookup = lookup.ToLowerInvariant().Replace("°", ""); - int i = 1; - while (table.IsKnownUnit(i)) - { - if (wellformedLookup == table.GetUnitName(i).ToLowerInvariant().Replace("°", "") || - wellformedLookup == table.GetUnitPlural(i).ToLowerInvariant().Replace("°", "") || - wellformedLookup == table.GetUnitSymbol(i).ToLowerInvariant().Replace("°", "")) - { - return i; - } - i++; - } - return 0; - } - - private static List unitTables; - - private static CurrencyExchangeRatesProvider exchangeRateProvider; - - private static CurrencyExchangeTable currTable; - - private static DateTime lastChanged; - } -} diff --git a/NadekoBot/Modules/Searches/Commands/EvalCommand.cs b/NadekoBot/Modules/Searches/Commands/EvalCommand.cs deleted file mode 100644 index 4dd5a9fe..00000000 --- a/NadekoBot/Modules/Searches/Commands/EvalCommand.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Discord.Commands; -using Mathos.Parser; -using NadekoBot.Classes; -using System; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Searches.Commands -{ - class CalcCommand : DiscordCommand - { - public CalcCommand(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "calculate") - .Alias(Module.Prefix + "calc") - .Description($"Evaluate a mathematical expression. | `{Prefix}calc 1+1`") - .Parameter("expression", ParameterType.Unparsed) - .Do(EvalFunc()); - } - - - private CustomParser parser = new CustomParser(); - private Func EvalFunc() => async e => - { - string expression = e.GetArg("expression")?.Trim(); - if (string.IsNullOrWhiteSpace(expression)) - { - return; - } - string answer = Evaluate(expression); - if (answer == null) - { - await e.Channel.SendMessage($"Expression {expression} failed to evaluate"); - return; - } - await e.Channel.SendMessage($"⚙ `{answer}`"); - }; - - private string Evaluate(string expression) - { - //check for factorial - expression = Regex.Replace(expression, @"\d+!", x => x.Value + "0"); - try - { - string result = parser.Parse(expression).ToString(); - return result; - } - catch (OverflowException) - { - return $"Overflow error on {expression}"; - } - catch (FormatException) - { - return $"\"{expression}\" was not formatted correctly"; - } - } - - - - class CustomParser : MathParser - { - public CustomParser() : base() - { - OperatorList.Add("!"); - OperatorAction.Add("!", (x, y) => Factorial(x)); - } - - static decimal Factorial(decimal x) - { - decimal y = x - 1; - while (y > 0) - { - x = x * y--; - } - return x; - } - } - - - } -} diff --git a/NadekoBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs b/NadekoBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs deleted file mode 100644 index 87397dbf..00000000 --- a/NadekoBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs +++ /dev/null @@ -1,63 +0,0 @@ -using NadekoBot.Extensions; -using System.Collections.Generic; -using System.Web; - -namespace NadekoBot.Modules.Searches.Commands.IMDB -{ - public class ImdbMovie - { - public bool Status { get; set; } - public string Id { get; set; } - public string Title { get; set; } - public string OriginalTitle { get; set; } - public string Year { get; set; } - public string Rating { get; set; } - public string Plot { get; set; } - public string Poster { get; set; } - public List Genres { get; set; } - //public ArrayList Directors { get; set; } - //public ArrayList Writers { get; set; } - //public ArrayList Cast { get; set; } - //public ArrayList Producers { get; set; } - //public ArrayList Musicians { get; set; } - //public ArrayList Cinematographers { get; set; } - //public ArrayList Editors { get; set; } - //public string MpaaRating { get; set; } - //public string ReleaseDate { get; set; } - //public ArrayList PlotKeywords { get; set; } - //public string PosterLarge { get; set; } - //public string PosterFull { get; set; } - //public string Runtime { get; set; } - //public string Top250 { get; set; } - //public string Oscars { get; set; } - //public string Awards { get; set; } - //public string Nominations { get; set; } - //public string Storyline { get; set; } - //public string Tagline { get; set; } - //public string Votes { get; set; } - //public ArrayList Languages { get; set; } - //public ArrayList Countries { get; set; } - //public Dictionary ReleaseDates { get; set; } - //public ArrayList MediaImages { get; set; } - //public ArrayList RecommendedTitles { get; set; } - public string ImdbURL { get; set; } - - public Dictionary Aka { get; set; } - - public override string ToString() => -$@"`Title:` {HttpUtility.HtmlDecode(Title)} {(string.IsNullOrEmpty(OriginalTitle) ? "" : $"({OriginalTitle})")} -`Year:` {Year} -`Rating:` {Rating} -`Genre:` {GenresAsString} -`Link:` <{ImdbURL}> -`Plot:` {System.Net.WebUtility.HtmlDecode(Plot.TrimTo(500))} -`img:` " + Poster.ShortenUrl().Result; - - //public string EnglishTitle => Aka.ContainsKey("USA") ? Aka["USA"] : - // (Aka.ContainsKey("UK") ? Aka["UK"] : - // (Aka.ContainsKey("(original title)") ? Aka["(original title)"] : - // (Aka.ContainsKey("(original)") ? Aka["(original)"] : OriginalTitle))); - public string GenresAsString => - string.Join(", ", Genres); - } -} \ No newline at end of file diff --git a/NadekoBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs b/NadekoBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs deleted file mode 100644 index 5226d2a4..00000000 --- a/NadekoBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text; -using System.Text.RegularExpressions; - -/******************************************************************************* -* Free ASP.net IMDb Scraper API for the new IMDb Template. -* Author: Abhinay Rathore -* Website: http://www.AbhinayRathore.com -* Blog: http://web3o.blogspot.com -* More Info: http://web3o.blogspot.com/2010/11/aspnetc-imdb-scraping-api.html - -* Updated By: Gergo Torcsvari -* Last Updated: Feb, 2016 -*******************************************************************************/ - -namespace NadekoBot.Modules.Searches.Commands.IMDB -{ - public static class ImdbScraper - { - //Search Engine URLs - private static string GoogleSearch = "https://www.google.com/search?q=imdb+"; - private static string BingSearch = "http://www.bing.com/search?q=imdb+"; - private static string AskSearch = "http://www.ask.com/web?q=imdb+"; - //Constructor - public static ImdbMovie ImdbScrape(string MovieName, bool GetExtraInfo = true) - { - ImdbMovie mov = new ImdbMovie(); - string imdbUrl = GetIMDbUrl(System.Uri.EscapeUriString(MovieName)); - mov.Status = false; - if (!string.IsNullOrWhiteSpace(imdbUrl)) - { - ParseIMDbPage(imdbUrl, GetExtraInfo, mov); - } - - return mov; - } - - public static ImdbMovie ImdbScrapeFromId(string imdbId, bool GetExtraInfo = true) - { - ImdbMovie mov = new ImdbMovie(); - string imdbUrl = "http://www.imdb.com/title/" + imdbId + "/"; - mov.Status = false; - ParseIMDbPage(imdbUrl, GetExtraInfo, mov); - return mov; - } - - public static string GetIMDBId(string MovieName) - { - string imdbUrl = GetIMDbUrl(System.Uri.EscapeUriString(MovieName)); - return match(@"http://www.imdb.com/title/(tt\d{7})", imdbUrl); - } - //Get IMDb URL from search results - private static string GetIMDbUrl(string MovieName, string searchEngine = "google") - { - string url = GoogleSearch + MovieName; //default to Google search - if (searchEngine.ToLower().Equals("bing")) url = BingSearch + MovieName; - if (searchEngine.ToLower().Equals("ask")) url = AskSearch + MovieName; - string html = GetUrlData(url); - ArrayList imdbUrls = MatchAll(@".*?", html); - if (imdbUrls.Count > 0) - return (string)imdbUrls[0]; //return first IMDb result - else if (searchEngine.ToLower().Equals("google")) //if Google search fails - return GetIMDbUrl(MovieName, "bing"); //search using Bing - else if (searchEngine.ToLower().Equals("bing")) //if Bing search fails - return GetIMDbUrl(MovieName, "ask"); //search using Ask - else //search fails - return string.Empty; - } - //Parse IMDb page data - private static void ParseIMDbPage(string imdbUrl, bool GetExtraInfo, ImdbMovie mov) - { - string html = GetUrlData(imdbUrl + "combined"); - mov.Id = match(@"", html); - if (!string.IsNullOrEmpty(mov.Id)) - { - mov.Status = true; - mov.Title = match(@"(IMDb \- )*(.*?) \(.*?", html, 2); - mov.OriginalTitle = match(@"title-extra"">(.*?)<", html); - mov.Year = match(@".*?\(.*?(\d{4}).*?\).*?", match(@"(.*?)", html)); - mov.Rating = match(@"(\d.\d)/10", html); - mov.Genres = MatchAll(@"(.*?)", match(@"Genre.?:(.*?)(|See more)", html)).Cast().ToList(); - mov.Plot = match(@"Plot:.*?
(.*?)((.*?)", match(@"Directed by(.*?)", html)); - //mov.Writers = matchAll(@"(.*?)", match(@"Writing credits(.*?)", html)); - //mov.Producers = matchAll(@"(.*?)", match(@"Produced by(.*?)", html)); - //mov.Musicians = matchAll(@"(.*?)", match(@"Original Music by(.*?)", html)); - //mov.Cinematographers = matchAll(@"(.*?)", match(@"Cinematography by(.*?)", html)); - //mov.Editors = matchAll(@"(.*?)", match(@"Film Editing by(.*?)", html)); - //mov.Cast = matchAll(@"(.*?)", match(@"

Cast

(.*?)", html)); - //mov.PlotKeywords = matchAll(@"(.*?)", match(@"Plot Keywords:.*?
(.*?).*?
.*?(\d{1,2} (January|February|March|April|May|June|July|August|September|October|November|December) (19|20)\d{2})", html); - //mov.Runtime = match(@"Runtime:
(\d{1,4}) min[\s]*.*?
", html); - //mov.Top250 = match(@"Top 250: #(\d{1,3})<", html); - //mov.Oscars = match(@"Won (\d+) Oscars?\.", html); - //if (string.IsNullOrEmpty(mov.Oscars) && "Won Oscar.".Equals(match(@"(Won Oscar\.)", html))) mov.Oscars = "1"; - //mov.Awards = match(@"(\d{1,4}) wins", html); - //mov.Nominations = match(@"(\d{1,4}) nominations", html); - //mov.Tagline = match(@"Tagline:.*?
(.*?)(:
Rated (G|PG|PG-13|PG-14|R|NC-17|X) ", html); - //mov.Votes = match(@">(\d+,?\d*) votes<", html); - //mov.Languages = matchAll(@"(.*?)", match(@"Language.?:(.*?)(
|>.?and )", html)); - //mov.Countries = matchAll(@"(.*?)", match(@"Country:(.*?)(
|>.?and )", html)); - mov.Poster = match(@"
.*?", html); - if (!string.IsNullOrEmpty(mov.Poster) && mov.Poster.IndexOf("media-imdb.com") > 0) - { - mov.Poster = Regex.Replace(mov.Poster, @"_V1.*?.jpg", "_V1._SY200.jpg"); - //mov.PosterLarge = Regex.Replace(mov.Poster, @"_V1.*?.jpg", "_V1._SY500.jpg"); - //mov.PosterFull = Regex.Replace(mov.Poster, @"_V1.*?.jpg", "_V1._SY0.jpg"); - } - else - { - mov.Poster = string.Empty; - //mov.PosterLarge = string.Empty; - //mov.PosterFull = string.Empty; - } - mov.ImdbURL = "http://www.imdb.com/title/" + mov.Id + "/"; - if (GetExtraInfo) - { - string plotHtml = GetUrlData(imdbUrl + "plotsummary"); - //mov.Storyline = match(@"

(.*?)(|

)", plotHtml); - GetReleaseDatesAndAka(mov); - //mov.MediaImages = getMediaImages(mov); - //mov.RecommendedTitles = getRecommendedTitles(mov); - } - } - } - //Get all release dates and aka-s - private static void GetReleaseDatesAndAka(ImdbMovie mov) - { - Dictionary release = new Dictionary(); - string releasehtml = GetUrlData("http://www.imdb.com/title/" + mov.Id + "/releaseinfo"); - foreach (string r in MatchAll(@"(.*?)", match(@"\n*?(.*?)
", releasehtml))) - { - Match rd = new Regex(@"(.*?)\n*?.*?(.*?)", RegexOptions.Multiline).Match(r); - release[StripHTML(rd.Groups[1].Value.Trim())] = StripHTML(rd.Groups[2].Value.Trim()); - } - //mov.ReleaseDates = release; - - Dictionary aka = new Dictionary(); - ArrayList list = MatchAll(@".*?(.*?)", match(@"\n*?(.*?)
", releasehtml)); - foreach (string r in list) - { - Match rd = new Regex(@"\n*?.*?(.*?)\n*?.*?(.*?)", RegexOptions.Multiline).Match(r); - aka[StripHTML(rd.Groups[1].Value.Trim())] = StripHTML(rd.Groups[2].Value.Trim()); - } - mov.Aka = aka; - - - - } - //Get all media images - private static ArrayList GetMediaImages(ImdbMovie mov) - { - ArrayList list = new ArrayList(); - string mediaurl = "http://www.imdb.com/title/" + mov.Id + "/mediaindex"; - string mediahtml = GetUrlData(mediaurl); - int pagecount = MatchAll(@"
", match(@"(.*?)", mediahtml)).Count; - for (int p = 1; p <= pagecount + 1; p++) - { - mediahtml = GetUrlData(mediaurl + "?page=" + p); - foreach (Match m in new Regex(@"src=""(.*?)""", RegexOptions.Multiline).Matches(match(@"
(.*?)
", mediahtml))) - { - String image = m.Groups[1].Value; - list.Add(Regex.Replace(image, @"_V1\..*?.jpg", "_V1._SY0.jpg")); - } - } - return list; - } - //Get Recommended Titles - private static ArrayList GetRecommendedTitles(ImdbMovie mov) - { - ArrayList list = new ArrayList(); - string recUrl = "http://www.imdb.com/widget/recommendations/_ajax/get_more_recs?specs=p13nsims%3A" + mov.Id; - string json = GetUrlData(recUrl); - list = MatchAll(@"title=\\""(.*?)\\""", json); - HashSet set = new HashSet(); - foreach (String rec in list) set.Add(rec); - return new ArrayList(set.ToList()); - } - /*******************************[ Helper Methods ]********************************/ - //Match single instance - private static string match(string regex, string html, int i = 1) - { - return new Regex(regex, RegexOptions.Multiline).Match(html).Groups[i].Value.Trim(); - } - //Match all instances and return as ArrayList - private static ArrayList MatchAll(string regex, string html, int i = 1) - { - ArrayList list = new ArrayList(); - foreach (Match m in new Regex(regex, RegexOptions.Multiline).Matches(html)) - list.Add(m.Groups[i].Value.Trim()); - return list; - } - //Strip HTML Tags - private static string StripHTML(string inputString) - { - return Regex.Replace(inputString, @"<.*?>", string.Empty); - } - //Get URL Data - private static string GetUrlData(string url) - { - WebClient client = new WebClient(); - Random r = new Random(); - //Random IP Address - //client.Headers["X-Forwarded-For"] = r.Next(0, 255) + "." + r.Next(0, 255) + "." + r.Next(0, 255) + "." + r.Next(0, 255); - //Random User-Agent - client.Headers["User-Agent"] = "Mozilla/" + r.Next(3, 5) + ".0 (Windows NT " + r.Next(3, 5) + "." + r.Next(0, 2) + "; rv:37.0) Gecko/20100101 Firefox/" + r.Next(30, 37) + "." + r.Next(0, 5); - Stream datastream = client.OpenRead(url); - StreamReader reader = new StreamReader(datastream); - StringBuilder sb = new StringBuilder(); - - //TODO: Coud be reader error must catch and drop!!! - while (!reader.EndOfStream) - sb.Append(reader.ReadLine()); - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/NadekoBot/Modules/Searches/Commands/LoLCommands.cs b/NadekoBot/Modules/Searches/Commands/LoLCommands.cs deleted file mode 100644 index 5a5b766a..00000000 --- a/NadekoBot/Modules/Searches/Commands/LoLCommands.cs +++ /dev/null @@ -1,383 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Searches.Commands -{ - internal class LoLCommands : DiscordCommand - { - - private class CachedChampion - { - public System.IO.Stream ImageStream { get; set; } - public DateTime AddedAt { get; set; } - public string Name { get; set; } - } - - private class ChampionNameComparer : IEqualityComparer - { - public bool Equals(JToken a, JToken b) => a["name"].ToString() == b["name"].ToString(); - - public int GetHashCode(JToken obj) => - obj["name"].GetHashCode(); - } - - private static Dictionary CachedChampionImages = new Dictionary(); - - private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer(); - public LoLCommands(DiscordModule module) : base(module) - { - clearTimer.Interval = new TimeSpan(0, 10, 0).TotalMilliseconds; - clearTimer.Start(); - clearTimer.Elapsed += (s, e) => - { - try - { - CachedChampionImages = CachedChampionImages - .Where(kvp => DateTime.Now - kvp.Value.AddedAt > new TimeSpan(1, 0, 0)) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - } - catch { } - }; - } - - private readonly string[] trashTalk = { "Better ban your counters. You are going to carry the game anyway.", - "Go with the flow. Don't think. Just ban one of these.", - "DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.", - "Ask your teammates what would they like to play, and ban that.", - "If you consider playing teemo, do it. If you consider teemo, you deserve him.", - "Doesn't matter what you ban really. Enemy will ban your main and you will lose." }; - - public Func DoFunc() - { - throw new NotImplementedException(); - } - - private class MatchupModel - { - public int Games { get; set; } - public float WinRate { get; set; } - [Newtonsoft.Json.JsonProperty("key")] - public string Name { get; set; } - public float StatScore { get; set; } - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "lolchamp") - .Description($"Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. |`{Prefix}lolchamp Riven` or `{Prefix}lolchamp Annie sup`") - .Parameter("champ", ParameterType.Required) - .Parameter("position", ParameterType.Unparsed) - .Do(async e => - { - try - { - //get role - var role = ResolvePos(e.GetArg("position")); - var resolvedRole = role; - var name = e.GetArg("champ").Replace(" ", "").ToLower(); - CachedChampion champ = null; - - if(CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ)) - if (champ != null) - { - champ.ImageStream.Position = 0; - await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); - return; - } - var allData = JArray.Parse(await Classes.SearchHelper.GetResponseStringAsync($"http://api.champion.gg/champion/{name}?api_key={NadekoBot.Creds.LOLAPIKey}").ConfigureAwait(false)); - JToken data = null; - if (role != null) - { - for (var i = 0; i < allData.Count; i++) - { - if (allData[i]["role"].ToString().Equals(role)) - { - data = allData[i]; - break; - } - } - if (data == null) - { - await e.Channel.SendMessage("💢 Data for that role does not exist.").ConfigureAwait(false); - return; - } - } - else - { - data = allData[0]; - role = allData[0]["role"].ToString(); - resolvedRole = ResolvePos(role); - } - if(CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ)) - if (champ != null) - { - champ.ImageStream.Position = 0; - await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); - return; - } - //name = data["title"].ToString(); - // get all possible roles, and "select" the shown one - var roles = new string[allData.Count]; - for (var i = 0; i < allData.Count; i++) - { - roles[i] = allData[i]["role"].ToString(); - if (roles[i] == role) - roles[i] = ">" + roles[i] + "<"; - } - var general = JArray.Parse(await SearchHelper.GetResponseStringAsync($"http://api.champion.gg/stats/" + - $"champs/{name}?api_key={NadekoBot.Creds.LOLAPIKey}") - .ConfigureAwait(false)) - .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"]; - if (general == null) - { - Console.WriteLine("General is null."); - return; - } - //get build data for this role - var buildData = data["items"]["mostGames"]["items"]; - var items = new string[6]; - for (var i = 0; i < 6; i++) - { - items[i] = buildData[i]["id"].ToString(); - } - - //get matchup data to show counters and countered champions - var matchupDataIE = data["matchups"].ToObject>(); - - var matchupData = matchupDataIE.OrderBy(m => m.StatScore).ToArray(); - - var countered = new[] { matchupData[0].Name, matchupData[1].Name, matchupData[2].Name }; - var counters = new[] { matchupData[matchupData.Length - 1].Name, matchupData[matchupData.Length - 2].Name, matchupData[matchupData.Length - 3].Name }; - - //get runes data - var runesJArray = data["runes"]["mostGames"]["runes"] as JArray; - var runes = string.Join("\n", runesJArray.OrderBy(jt => int.Parse(jt["number"].ToString())).Select(jt => jt["number"].ToString() + "x" + jt["name"])); - - // get masteries data - - var masteries = (data["masteries"]["mostGames"]["masteries"] as JArray); - - //get skill order data - - var orderArr = (data["skills"]["mostGames"]["order"] as JArray); - - var img = Image.FromFile("data/lol/bg.png"); - using (var g = Graphics.FromImage(img)) - { - g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; - //g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy; - const int margin = 5; - const int imageSize = 75; - var normalFont = new Font("Monaco", 8, FontStyle.Regular); - var smallFont = new Font("Monaco", 7, FontStyle.Regular); - //draw champ image - var champName = data["key"].ToString().Replace(" ", ""); - - g.DrawImage(GetImage(champName), new Rectangle(margin, margin, imageSize, imageSize)); - //draw champ name - if (champName == "MonkeyKing") - champName = "Wukong"; - g.DrawString($"{champName}", new Font("Times New Roman", 24, FontStyle.Regular), Brushes.WhiteSmoke, margin + imageSize + margin, margin); - //draw champ surname - - //draw skill order - if (orderArr.Count != 0) - { - float orderFormula = 120 / orderArr.Count; - const float orderVerticalSpacing = 10; - for (var i = 0; i < orderArr.Count; i++) - { - var orderX = margin + margin + imageSize + orderFormula * i + i; - float orderY = margin + 35; - var spellName = orderArr[i].ToString().ToLowerInvariant(); - - switch (spellName) - { - case "w": - orderY += orderVerticalSpacing; - break; - case "e": - orderY += orderVerticalSpacing * 2; - break; - case "r": - orderY += orderVerticalSpacing * 3; - break; - default: - break; - } - - g.DrawString(spellName.ToUpperInvariant(), new Font("Monaco", 7), Brushes.LimeGreen, orderX, orderY); - } - } - //draw roles - g.DrawString("Roles: " + string.Join(", ", roles), normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin); - - //draw average stats - g.DrawString( -$@" Average Stats - -Kills: {general["kills"]} CS: {general["minionsKilled"]} -Deaths: {general["deaths"]} Win: {general["winPercent"]}% -Assists: {general["assists"]} Ban: {general["banRate"]}% -", normalFont, Brushes.WhiteSmoke, img.Width - 150, margin); - //draw masteries - g.DrawString($"Masteries: {string.Join(" / ", masteries?.Select(jt => jt["total"]))}", normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 20); - //draw runes - g.DrawString($"{runes}", smallFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 40); - //draw counters - g.DrawString($"Best against", smallFont, Brushes.WhiteSmoke, margin, img.Height - imageSize + margin); - var smallImgSize = 50; - - for (var i = 0; i < counters.Length; i++) - { - g.DrawImage(GetImage(counters[i]), - new Rectangle(i * (smallImgSize + margin) + margin, img.Height - smallImgSize - margin, - smallImgSize, - smallImgSize)); - } - //draw countered by - g.DrawString($"Worst against", smallFont, Brushes.WhiteSmoke, img.Width - 3 * (smallImgSize + margin), img.Height - imageSize + margin); - - for (var i = 0; i < countered.Length; i++) - { - var j = countered.Length - i; - g.DrawImage(GetImage(countered[i]), - new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), img.Height - smallImgSize - margin, - smallImgSize, - smallImgSize)); - } - //draw item build - g.DrawString("Popular build", normalFont, Brushes.WhiteSmoke, img.Width - (3 * (smallImgSize + margin) + margin), 77); - - for (var i = 0; i < 6; i++) - { - var inverseI = 5 - i; - var j = inverseI % 3 + 1; - var k = inverseI / 3; - g.DrawImage(GetImage(items[i], GetImageType.Item), - new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), 92 + k * (smallImgSize + margin), - smallImgSize, - smallImgSize)); - } - } - var cachedChamp = new CachedChampion { AddedAt = DateTime.Now, ImageStream = img.ToStream(System.Drawing.Imaging.ImageFormat.Png), Name = name.ToLower() + "_" + resolvedRole }; - CachedChampionImages.Add(cachedChamp.Name, cachedChamp); - await e.Channel.SendFile(data["title"] + "_stats.png", cachedChamp.ImageStream).ConfigureAwait(false); - } - catch (Exception ex) - { - Console.WriteLine(ex); - await e.Channel.SendMessage("💢 Failed retreiving data for that champion.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Module.Prefix + "lolban") - .Description($"Shows top 6 banned champions ordered by ban rate. Ban these champions and you will be Plat 5 in no time. | `{Prefix}lolban`") - .Do(async e => - { - - var showCount = 8; - //http://api.champion.gg/stats/champs/mostBanned?api_key=YOUR_API_TOKEN&page=1&limit=2 - try - { - var data = JObject.Parse( - await Classes - .SearchHelper - .GetResponseStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" + - $"api_key={NadekoBot.Creds.LOLAPIKey}&page=1&" + - $"limit={showCount}") - .ConfigureAwait(false))["data"] as JArray; - var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList(); - var sb = new StringBuilder(); - sb.AppendLine($"**Showing {showCount} top banned champions.**"); - sb.AppendLine($"`{trashTalk[new Random().Next(0, trashTalk.Length)]}`"); - for (var i = 0; i < dataList.Count; i++) - { - if (i % 2 == 0 && i != 0) - sb.AppendLine(); - sb.Append($"`{i + 1}.` **{dataList[i]["name"]}** "); - //sb.AppendLine($" ({dataList[i]["general"]["banRate"]}%)"); - } - - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - } - catch (Exception) - { - await e.Channel.SendMessage($":anger: Fail: Champion.gg didsabled ban data until next patch. Sorry for the inconvenience.").ConfigureAwait(false); - } - }); - } - - private enum GetImageType - { - Champion, - Item - } - private static Image GetImage(string id, GetImageType imageType = GetImageType.Champion) - { - try - { - switch (imageType) - { - case GetImageType.Champion: - return Image.FromFile($"data/lol/champions/{id}.png"); - case GetImageType.Item: - default: - return Image.FromFile($"data/lol/items/{id}.png"); - } - } - catch (Exception) - { - return Image.FromFile("data/lol/_ERROR.png"); - } - } - - private static string ResolvePos(string pos) - { - if (string.IsNullOrWhiteSpace(pos)) - return null; - switch (pos.ToLowerInvariant()) - { - case "m": - case "mid": - case "midorfeed": - case "midd": - case "middle": - return "Middle"; - case "top": - case "topp": - case "t": - case "toporfeed": - return "Top"; - case "j": - case "jun": - case "jungl": - case "jungle": - return "Jungle"; - case "a": - case "ad": - case "adc": - case "carry": - case "ad carry": - case "adcarry": - case "c": - return "ADC"; - case "s": - case "sup": - case "supp": - case "support": - return "Support"; - default: - return pos; - } - } - } -} diff --git a/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs b/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs deleted file mode 100644 index a7d23181..00000000 --- a/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace NadekoBot.Modules.Searches.Commands -{ - class MemegenCommands : DiscordCommand - { - public MemegenCommands(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Prefix + "memelist") - .Description($"Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `{Prefix}memelist`") - .Do(async e => - { - int i = 0; - await e.Channel.SendMessage("`List Of Commands:`\n```xl\n" + - string.Join("\n", JsonConvert.DeserializeObject>(await SearchHelper.GetResponseStringAsync("http://memegen.link/templates/")) - .Select(kvp => Path.GetFileName(kvp.Value)) - .GroupBy(item => (i++) / 4) - .Select(ig => string.Concat(ig.Select(el => $"{el,-17}")))) - + $"\n```").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "memegen") - .Description($"Generates a meme from memelist with top and bottom text. | `{Prefix}memegen biw \"gets iced coffee\" \"in the winter\"`") - .Parameter("meme", ParameterType.Required) - .Parameter("toptext", ParameterType.Required) - .Parameter("bottext", ParameterType.Required) - .Do(async e => - { - var meme = e.GetArg("meme"); - var top = Uri.EscapeDataString(e.GetArg("toptext").Replace(' ', '-')); - var bot = Uri.EscapeDataString(e.GetArg("bottext").Replace(' ', '-')); - await e.Channel.SendMessage($"http://memegen.link/{meme}/{top}/{bot}.jpg"); - }); - } - } -} diff --git a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs b/NadekoBot/Modules/Searches/Commands/OsuCommands.cs deleted file mode 100644 index 6d95b72e..00000000 --- a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs +++ /dev/null @@ -1,269 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using Newtonsoft.Json.Linq; -using System; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text.RegularExpressions; - -namespace NadekoBot.Modules.Searches.Commands -{ - internal class OsuCommands : DiscordCommand - { - public OsuCommands(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "osu") - .Description($"Shows osu stats for a player. | `{Prefix}osu Name` or `{Prefix}osu Name taiko`") - .Parameter("usr", ParameterType.Required) - .Parameter("mode", ParameterType.Unparsed) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("usr"))) - return; - - using (WebClient cl = new WebClient()) - { - try - { - var m = 0; - if (!string.IsNullOrWhiteSpace(e.GetArg("mode"))) - { - m = ResolveGameMode(e.GetArg("mode")); - } - - cl.CachePolicy = new System.Net.Cache.RequestCachePolicy(System.Net.Cache.RequestCacheLevel.NoCacheNoStore); - cl.Headers.Add(HttpRequestHeader.UserAgent, "Mozilla/5.0 (Windows NT 6.2; Win64; x64)"); - cl.DownloadDataAsync(new Uri($"http://lemmmy.pw/osusig/sig.php?uname={ e.GetArg("usr") }&flagshadow&xpbar&xpbarhex&pp=2&mode={m}")); - cl.DownloadDataCompleted += async (s, cle) => - { - try - { - await e.Channel.SendFile($"{e.GetArg("usr")}.png", new MemoryStream(cle.Result)).ConfigureAwait(false); - await e.Channel.SendMessage($"`Profile Link:`https://osu.ppy.sh/u/{Uri.EscapeDataString(e.GetArg("usr"))}\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false); - } - catch { } - }; - } - catch - { - await e.Channel.SendMessage("💢 Failed retrieving osu signature :\\").ConfigureAwait(false); - } - } - }); - - cgb.CreateCommand(Module.Prefix + "osu b") - .Description($"Shows information about an osu beatmap. |`{Prefix}osu b https://osu.ppy.sh/s/127712`") - .Parameter("map", ParameterType.Unparsed) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.OsuAPIKey)) - { - await e.Channel.SendMessage("💢 An osu! API key is required.").ConfigureAwait(false); - return; - } - - if (string.IsNullOrWhiteSpace(e.GetArg("map"))) - return; - - try - { - var mapId = ResolveMap(e.GetArg("map")); - var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Creds.OsuAPIKey}&{mapId}"; - var obj = JArray.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false))[0]; - var sb = new System.Text.StringBuilder(); - var starRating = Math.Round(Double.Parse($"{obj["difficultyrating"]}", CultureInfo.InvariantCulture), 2); - var time = TimeSpan.FromSeconds(Double.Parse($"{obj["total_length"]}")).ToString(@"mm\:ss"); - sb.AppendLine($"{obj["artist"]} - {obj["title"]}, mapped by {obj["creator"]}. https://osu.ppy.sh/s/{obj["beatmapset_id"]}"); - sb.AppendLine($"{starRating} stars, {obj["bpm"]} BPM | AR{obj["diff_approach"]}, CS{obj["diff_size"]}, OD{obj["diff_overall"]} | Length: {time}"); - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Something went wrong."); - } - }); - - cgb.CreateCommand(Module.Prefix + "osu top5") - .Description($"Displays a user's top 5 plays. |`{Prefix}osu top5 Name`") - .Parameter("usr", ParameterType.Required) - .Parameter("mode", ParameterType.Unparsed) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.OsuAPIKey)) - { - await e.Channel.SendMessage("💢 An osu! API key is required.").ConfigureAwait(false); - return; - } - - if (string.IsNullOrWhiteSpace(e.GetArg("usr"))) - { - await e.Channel.SendMessage("💢 Please provide a username.").ConfigureAwait(false); - return; - } - - try - { - var m = 0; - if (!string.IsNullOrWhiteSpace(e.GetArg("mode"))) - { - m = ResolveGameMode(e.GetArg("mode")); - } - - var reqString = $"https://osu.ppy.sh/api/get_user_best?k={NadekoBot.Creds.OsuAPIKey}&u={Uri.EscapeDataString(e.GetArg("usr"))}&type=string&limit=5&m={m}"; - var obj = JArray.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false)); - var sb = new System.Text.StringBuilder($"`Top 5 plays for {e.GetArg("usr")}:`\n```xl" + Environment.NewLine); - foreach (var item in obj) - { - var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Creds.OsuAPIKey}&b={item["beatmap_id"]}"; - var map = JArray.Parse(await SearchHelper.GetResponseStringAsync(mapReqString).ConfigureAwait(false))[0]; - var pp = Math.Round(Double.Parse($"{item["pp"]}", CultureInfo.InvariantCulture), 2); - var acc = CalculateAcc(item, m); - var mods = ResolveMods(Int32.Parse($"{item["enabled_mods"]}")); - if (mods != "+") - sb.AppendLine($"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"],-40}) | **{mods,-10}** | /b/{item["beatmap_id"]}"); - else - sb.AppendLine($"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"],-40}) | /b/{item["beatmap_id"]}"); - } - sb.Append("```"); - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Something went wrong."); - } - }); - } - - //https://osu.ppy.sh/wiki/Accuracy - private static Double CalculateAcc(JToken play, int mode) - { - if (mode == 0) - { - var hitPoints = Double.Parse($"{play["count50"]}") * 50 + Double.Parse($"{play["count100"]}") * 100 + Double.Parse($"{play["count300"]}") * 300; - var totalHits = Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countmiss"]}"); - totalHits *= 300; - return Math.Round(hitPoints / totalHits * 100, 2); - } - else if (mode == 1) - { - var hitPoints = Double.Parse($"{play["countmiss"]}") * 0 + Double.Parse($"{play["count100"]}") * 0.5 + Double.Parse($"{play["count300"]}") * 1; - var totalHits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}"); - hitPoints *= 300; - totalHits *= 300; - return Math.Round(hitPoints / totalHits * 100, 2); - } - else if (mode == 2) - { - var fruitsCaught = Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}"); - var totalFruits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countkatu"]}"); - return Math.Round(fruitsCaught / totalFruits * 100, 2); - } - else - { - var hitPoints = Double.Parse($"{play["count50"]}") * 50 + Double.Parse($"{play["count100"]}") * 100 + Double.Parse($"{play["countkatu"]}") * 200 + (Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countgeki"]}")) * 300; - var totalHits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["countkatu"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countgeki"]}"); - totalHits *= 300; - return Math.Round(hitPoints / totalHits * 100, 2); - } - } - - private static string ResolveMap(string mapLink) - { - Match s = new Regex(@"osu.ppy.sh\/s\/", RegexOptions.IgnoreCase).Match(mapLink); - Match b = new Regex(@"osu.ppy.sh\/b\/", RegexOptions.IgnoreCase).Match(mapLink); - Match p = new Regex(@"osu.ppy.sh\/p\/", RegexOptions.IgnoreCase).Match(mapLink); - Match m = new Regex(@"&m=", RegexOptions.IgnoreCase).Match(mapLink); - if (s.Success) - { - var mapId = mapLink.Substring(mapLink.IndexOf("/s/") + 3); - return $"s={mapId}"; - } - else if (b.Success) - { - if (m.Success) - return $"b={mapLink.Substring(mapLink.IndexOf("/b/") + 3, mapLink.IndexOf("&m") - (mapLink.IndexOf("/b/") + 3))}"; - else - return $"b={mapLink.Substring(mapLink.IndexOf("/b/") + 3)}"; - } - else if (p.Success) - { - if (m.Success) - return $"b={mapLink.Substring(mapLink.IndexOf("?b=") + 3, mapLink.IndexOf("&m") - (mapLink.IndexOf("?b=") + 3))}"; - else - return $"b={mapLink.Substring(mapLink.IndexOf("?b=") + 3)}"; - } - else - { - return $"s={mapLink}"; //just a default incase an ID number was provided by itself (non-url)? - } - } - - private static int ResolveGameMode(string mode) - { - switch (mode.ToLower()) - { - case "std": - case "standard": - return 0; - case "taiko": - return 1; - case "ctb": - case "catchthebeat": - return 2; - case "mania": - case "osu!mania": - return 3; - default: - return 0; - } - } - - //https://github.com/ppy/osu-api/wiki#mods - private static string ResolveMods(int mods) - { - var modString = $"+"; - - if (IsBitSet(mods, 0)) - modString += "NF"; - if (IsBitSet(mods, 1)) - modString += "EZ"; - if (IsBitSet(mods, 8)) - modString += "HT"; - - if (IsBitSet(mods, 3)) - modString += "HD"; - if (IsBitSet(mods, 4)) - modString += "HR"; - if (IsBitSet(mods, 6) && !IsBitSet(mods, 9)) - modString += "DT"; - if (IsBitSet(mods, 9)) - modString += "NC"; - if (IsBitSet(mods, 10)) - modString += "FL"; - - if (IsBitSet(mods, 5)) - modString += "SD"; - if (IsBitSet(mods, 14)) - modString += "PF"; - - if (IsBitSet(mods, 7)) - modString += "RX"; - if (IsBitSet(mods, 11)) - modString += "AT"; - if (IsBitSet(mods, 12)) - modString += "SO"; - return modString; - } - - private static bool IsBitSet(int mods, int pos) - { - return (mods & (1 << pos)) != 0; - } - - } -} diff --git a/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs b/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs deleted file mode 100644 index b2c13dca..00000000 --- a/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.IO; - -namespace NadekoBot.Modules.Searches.Commands -{ - class PokemonSearchCommands : DiscordCommand - { - private static Dictionary pokemons; - private static Dictionary pokemonAbilities; - - public PokemonSearchCommands(DiscordModule module) : base(module) - { - - pokemons = JsonConvert.DeserializeObject>(File.ReadAllText("data/pokemon/pokemon_list.json")); - pokemonAbilities = JsonConvert.DeserializeObject>(File.ReadAllText("data/pokemon/pokemon_abilities.json")); - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Prefix + "pokemon") - .Alias(Prefix + "poke") - .Description($"Searches for a pokemon. | `{Prefix}poke Sylveon`") - .Parameter("pokemon", ParameterType.Unparsed) - .Do(async e => - { - var pok = e.GetArg("pokemon")?.Trim().ToUpperInvariant(); - if (string.IsNullOrWhiteSpace(pok)) - return; - - foreach (var kvp in pokemons) - { - if (kvp.Key.ToUpperInvariant() == pok.ToUpperInvariant()) - { - await e.Channel.SendMessage($"`Stats for \"{kvp.Key}\" pokemon:`\n{kvp.Value}"); - return; - } - } - await e.Channel.SendMessage("`No pokemon found.`"); - }); - - cgb.CreateCommand(Prefix + "pokemonability") - .Alias(Prefix + "pokeab") - .Description($"Searches for a pokemon ability. | `{Prefix}pokeab \"water gun\"`") - .Parameter("abil", ParameterType.Unparsed) - .Do(async e => - { - var ab = e.GetArg("abil")?.Trim().ToUpperInvariant().Replace(" ", ""); - if (string.IsNullOrWhiteSpace(ab)) - return; - foreach (var kvp in pokemonAbilities) - { - if (kvp.Key.ToUpperInvariant() == ab) - { - await e.Channel.SendMessage($"`Info for \"{kvp.Key}\" ability:`\n{kvp.Value}"); - return; - } - } - await e.Channel.SendMessage("`No ability found.`"); - }); - } - } - - public class SearchPokemon - { - public class GenderRatioClass - { - public float M { get; set; } - public float F { get; set; } - } - public class BaseStatsClass - { - public int HP { get; set; } - public int ATK { get; set; } - public int DEF { get; set; } - public int SPA { get; set; } - public int SPD { get; set; } - public int SPE { get; set; } - - public override string ToString() => $@" - **HP:** {HP,-4} **ATK:** {ATK,-4} **DEF:** {DEF,-4} - **SPA:** {SPA,-4} **SPD:** {SPD,-4} **SPE:** {SPE,-4}"; - } - public int Id { get; set; } - public string Species { get; set; } - public string[] Types { get; set; } - public GenderRatioClass GenderRatio { get; set; } - public BaseStatsClass BaseStats { get; set; } - public Dictionary Abilities { get; set; } - public float HeightM { get; set; } - public float WeightKg { get; set; } - public string Color { get; set; } - public string[] Evos { get; set; } - public string[] EggGroups { get; set; } - - public override string ToString() => $@"`Name:` {Species} -`Types:` {string.Join(", ", Types)} -`Stats:` {BaseStats} -`Height:` {HeightM,4}m `Weight:` {WeightKg}kg -`Abilities:` {string.Join(", ", Abilities.Values)}"; - - } - - public class SearchPokemonAbility - { - public string Desc { get; set; } - public string Name { get; set; } - public float Rating { get; set; } - - public override string ToString() => $@"`Name:` : {Name} -`Rating:` {Rating} -`Description:` {Desc}"; - } -} diff --git a/NadekoBot/Modules/Searches/Commands/RedditCommand.cs b/NadekoBot/Modules/Searches/Commands/RedditCommand.cs deleted file mode 100644 index 7a17d347..00000000 --- a/NadekoBot/Modules/Searches/Commands/RedditCommand.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; - -namespace NadekoBot.Modules.Searches.Commands -{ - class RedditCommand : DiscordCommand - { - public RedditCommand(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - //throw new NotImplementedException(); - } - } -} diff --git a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs b/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs deleted file mode 100644 index f1bfb084..00000000 --- a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs +++ /dev/null @@ -1,373 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Modules.Permissions.Classes; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Searches.Commands -{ - internal class StreamNotifications : DiscordCommand - { - private ConcurrentDictionary> cachedStatuses = new ConcurrentDictionary>(); - private bool FirstPass { get; set; } = true; - - public StreamNotifications(DiscordModule module) : base(module) - { - //start checking only after ready, because we need all servers to be initialized - NadekoBot.OnReady += () => - { - Task.Run(async () => - { - await Task.Delay(60000); - while (true) - { - cachedStatuses.Clear(); - try - { - var streams = SpecificConfigurations.Default.AllConfigs.SelectMany(c => c.ObservingStreams); - if (!streams.Any()) return; -#if NADEKO_RELEASE - var clr = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Blue; - Console.WriteLine($"Getting {streams.Count()} streams."); - Console.ForegroundColor = clr; -#endif - foreach (var stream in streams) - { - Tuple data; - try - { - data = await GetStreamStatus(stream).ConfigureAwait(false); - } - catch - { - continue; - } - - if (data.Item1 != stream.LastStatus) - { - stream.LastStatus = data.Item1; - if (FirstPass) - continue; - var server = NadekoBot.Client.GetServer(stream.ServerId); - var channel = server?.GetChannel(stream.ChannelId); - if (channel == null) - continue; - var msg = $"`{stream.Username}`'s stream is now " + - $"**{(data.Item1 ? "ONLINE" : "OFFLINE")}** with " + - $"**{data.Item2}** viewers."; - if (stream.LastStatus) - if (stream.Type == StreamNotificationConfig.StreamType.Hitbox) - msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】"; - else if (stream.Type == StreamNotificationConfig.StreamType.Twitch) - msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】"; - else if (stream.Type == StreamNotificationConfig.StreamType.Beam) - msg += $"\n`Here is the Link:`【 http://www.beam.pro/{stream.Username}/ 】"; - else if (stream.Type == StreamNotificationConfig.StreamType.YoutubeGaming) - msg += $"\n`Here is the Link:`【 not implemented yet - {stream.Username} 】"; - await channel.SendMessage(msg).ConfigureAwait(false); - } - } - FirstPass = false; -#if NADEKO_RELEASE - clr = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Blue; - Console.WriteLine($"Done getting streams."); - Console.ForegroundColor = clr; -#endif - } - catch { } - finally - { - await Task.Delay(TimeSpan.FromSeconds(60)); - } - } - }); - - }; - } - private async Task> GetStreamStatus(StreamNotificationConfig stream, bool checkCache = true) - { - bool isLive; - string response; - JObject data; - Tuple result; - switch (stream.Type) - { - case StreamNotificationConfig.StreamType.Hitbox: - var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username}"; - if (checkCache && cachedStatuses.TryGetValue(hitboxUrl, out result)) - return result; - response = await SearchHelper.GetResponseStringAsync(hitboxUrl).ConfigureAwait(false); - data = JObject.Parse(response); - isLive = data["media_is_live"].ToString() == "1"; - result = new Tuple(isLive, data["media_views"].ToString()); - cachedStatuses.TryAdd(hitboxUrl, result); - return result; - case StreamNotificationConfig.StreamType.Twitch: - var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6"; - if (checkCache && cachedStatuses.TryGetValue(twitchUrl, out result)) - return result; - response = await SearchHelper.GetResponseStringAsync(twitchUrl).ConfigureAwait(false); - data = JObject.Parse(response); - isLive = !string.IsNullOrWhiteSpace(data["stream"].ToString()); - result = new Tuple(isLive, isLive ? data["stream"]["viewers"].ToString() : stream.Username); - cachedStatuses.TryAdd(twitchUrl, result); - return result; - case StreamNotificationConfig.StreamType.Beam: - var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username}"; - if (checkCache && cachedStatuses.TryGetValue(beamUrl, out result)) - return result; - response = await SearchHelper.GetResponseStringAsync(beamUrl).ConfigureAwait(false); - data = JObject.Parse(response); - isLive = data["online"].ToObject() == true; - result = new Tuple(isLive, data["viewersCurrent"].ToString()); - cachedStatuses.TryAdd(beamUrl, result); - return result; - default: - break; - } - return new Tuple(false, "NOT_FOUND"); - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "hitbox") - .Alias(Module.Prefix + "hb") - .Description("Notifies this channel when a certain user starts streaming." + - $" | `{Prefix}hitbox SomeStreamer`") - .Parameter("username", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.ManageServer()) - .Do(TrackStream(StreamNotificationConfig.StreamType.Hitbox)); - - cgb.CreateCommand(Module.Prefix + "twitch") - .Alias(Module.Prefix + "tw") - .Description("Notifies this channel when a certain user starts streaming." + - $" | `{Prefix}twitch SomeStreamer`") - .AddCheck(SimpleCheckers.ManageServer()) - .Parameter("username", ParameterType.Unparsed) - .Do(TrackStream(StreamNotificationConfig.StreamType.Twitch)); - - cgb.CreateCommand(Module.Prefix + "beam") - .Alias(Module.Prefix + "bm") - .Description("Notifies this channel when a certain user starts streaming." + - $" | `{Prefix}beam SomeStreamer`") - .AddCheck(SimpleCheckers.ManageServer()) - .Parameter("username", ParameterType.Unparsed) - .Do(TrackStream(StreamNotificationConfig.StreamType.Beam)); - - cgb.CreateCommand(Module.Prefix + "checkhitbox") - .Alias(Module.Prefix + "chhb") - .Description("Checks if a certain user is streaming on the hitbox platform." + - $" | `{Prefix}chhb SomeStreamer`") - .Parameter("username", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - var stream = e.GetArg("username")?.Trim(); - if (string.IsNullOrWhiteSpace(stream)) - return; - try - { - var streamStatus = (await GetStreamStatus(new StreamNotificationConfig - { - Username = stream, - Type = StreamNotificationConfig.StreamType.Hitbox - })); - if (streamStatus.Item1) - { - await e.Channel.SendMessage($"`Streamer {stream} is online with {streamStatus.Item2} viewers.`"); - } - else - { - await e.Channel.SendMessage($"`Streamer {stream} is offline.`"); - } - } - catch - { - await e.Channel.SendMessage("No channel found."); - } - }); - - cgb.CreateCommand(Module.Prefix + "checktwitch") - .Alias(Module.Prefix + "chtw") - .Description("Checks if a certain user is streaming on the twitch platform." + - $" | `{Prefix}chtw SomeStreamer`") - .AddCheck(SimpleCheckers.ManageServer()) - .Parameter("username", ParameterType.Unparsed) - .Do(async e => - { - var stream = e.GetArg("username")?.Trim(); - if (string.IsNullOrWhiteSpace(stream)) - return; - try - { - var streamStatus = (await GetStreamStatus(new StreamNotificationConfig - { - Username = stream, - Type = StreamNotificationConfig.StreamType.Twitch - })); - if (streamStatus.Item1) - { - await e.Channel.SendMessage($"`Streamer {stream} is online with {streamStatus.Item2} viewers.`"); - } - else - { - await e.Channel.SendMessage($"`Streamer {stream} is offline.`"); - } - } - catch - { - await e.Channel.SendMessage("No channel found."); - } - }); - - cgb.CreateCommand(Module.Prefix + "checkbeam") - .Alias(Module.Prefix + "chbm") - .Description("Checks if a certain user is streaming on the beam platform." + - $" | `{Prefix}chbm SomeStreamer`") - .AddCheck(SimpleCheckers.ManageServer()) - .Parameter("username", ParameterType.Unparsed) - .Do(async e => - { - var stream = e.GetArg("username")?.Trim(); - if (string.IsNullOrWhiteSpace(stream)) - return; - try - { - var streamStatus = (await GetStreamStatus(new StreamNotificationConfig - { - Username = stream, - Type = StreamNotificationConfig.StreamType.Beam - })); - if (streamStatus.Item1) - { - await e.Channel.SendMessage($"`Streamer {stream} is online with {streamStatus.Item2} viewers.`"); - } - else - { - await e.Channel.SendMessage($"`Streamer {stream} is offline.`"); - } - } - catch - { - await e.Channel.SendMessage("No channel found."); - } - }); - - cgb.CreateCommand(Module.Prefix + "removestream") - .Alias(Module.Prefix + "rms") - .Description("Removes notifications of a certain streamer on this channel." + - $" | `{Prefix}rms SomeGuy`") - .AddCheck(SimpleCheckers.ManageServer()) - .Parameter("username", ParameterType.Unparsed) - .Do(async e => - { - var username = e.GetArg("username")?.ToLower().Trim(); - if (string.IsNullOrWhiteSpace(username)) - return; - - var config = SpecificConfigurations.Default.Of(e.Server.Id); - - var toRemove = config.ObservingStreams - .FirstOrDefault(snc => snc.ChannelId == e.Channel.Id && - snc.Username.ToLower().Trim() == username); - if (toRemove == null) - { - await e.Channel.SendMessage(":anger: No such stream.").ConfigureAwait(false); - return; - } - - config.ObservingStreams.Remove(toRemove); - await ConfigHandler.SaveConfig().ConfigureAwait(false); - await e.Channel.SendMessage($":ok: Removed `{toRemove.Username}`'s stream from notifications.").ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "liststreams") - .Alias(Module.Prefix + "ls") - .Description("Lists all streams you are following on this server." + - $" | `{Prefix}ls`") - .Do(async e => - { - - var config = SpecificConfigurations.Default.Of(e.Server.Id); - - var streams = config.ObservingStreams.Where(snc => - snc.ServerId == e.Server.Id); - - var streamsArray = streams as StreamNotificationConfig[] ?? streams.ToArray(); - - if (streamsArray.Length == 0) - { - await e.Channel.SendMessage("You are not following any streams on this server.").ConfigureAwait(false); - return; - } - - var text = string.Join("\n", streamsArray.Select(snc => - { - try - { - return $"`{snc.Username}`'s stream on **{e.Server.GetChannel(e.Channel.Id).Name}** channel. 【`{snc.Type.ToString()}`】"; - } - catch { } - return ""; - })); - - await e.Channel.SendMessage($"You are following **{streamsArray.Length}** streams on this server.\n\n" + text).ConfigureAwait(false); - }); - } - - private Func TrackStream(StreamNotificationConfig.StreamType type) => - async e => - { - var username = e.GetArg("username")?.ToLowerInvariant(); - if (string.IsNullOrWhiteSpace(username)) - return; - - var config = SpecificConfigurations.Default.Of(e.Server.Id); - - var stream = new StreamNotificationConfig - { - ServerId = e.Server.Id, - ChannelId = e.Channel.Id, - Username = username, - Type = type, - }; - var exists = config.ObservingStreams.Contains(stream); - if (exists) - { - await e.Channel.SendMessage(":anger: I am already notifying that stream on this channel.").ConfigureAwait(false); - return; - } - Tuple data; - try - { - data = await GetStreamStatus(stream).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage(":anger: Stream probably doesn't exist.").ConfigureAwait(false); - return; - } - var msg = $"Stream is currently **{(data.Item1 ? "ONLINE" : "OFFLINE")}** with **{data.Item2}** viewers"; - if (data.Item1) - if (type == StreamNotificationConfig.StreamType.Hitbox) - msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】"; - else if (type == StreamNotificationConfig.StreamType.Twitch) - msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】"; - else if (type == StreamNotificationConfig.StreamType.Beam) - msg += $"\n`Here is the Link:`【 https://beam.pro/{stream.Username}/ 】"; - else if (type == StreamNotificationConfig.StreamType.YoutubeGaming) - msg += $"\n`Here is the Link:` not implemented yet - {stream.Username}"; - stream.LastStatus = data.Item1; - if (!exists) - msg = $":ok: I will notify this channel when status changes.\n{msg}"; - await e.Channel.SendMessage(msg).ConfigureAwait(false); - config.ObservingStreams.Add(stream); - }; - } -} diff --git a/NadekoBot/Modules/Searches/Commands/WowJokes.cs b/NadekoBot/Modules/Searches/Commands/WowJokes.cs deleted file mode 100644 index b3e67eed..00000000 --- a/NadekoBot/Modules/Searches/Commands/WowJokes.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Classes.JSONModels; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace NadekoBot.Modules.Searches.Commands -{ - class WowJokeCommand : DiscordCommand - { - - List jokes = new List(); - - public WowJokeCommand(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - - cgb.CreateCommand(Module.Prefix + "wowjoke") - .Description($"Get one of Kwoth's penultimate WoW jokes. | `{Prefix}wowjoke`") - .Do(async e => - { - if (!jokes.Any()) - { - jokes = JsonConvert.DeserializeObject>(File.ReadAllText("data/wowjokes.json")); - } - await e.Channel.SendMessage(jokes[new Random().Next(0, jokes.Count)].ToString()); - }); - } - } -} diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/NadekoBot/Modules/Searches/SearchesModule.cs deleted file mode 100644 index acf8421e..00000000 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ /dev/null @@ -1,522 +0,0 @@ -using Discord; -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using NadekoBot.Modules.Searches.Commands; -using NadekoBot.Modules.Searches.Commands.IMDB; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Web; - -namespace NadekoBot.Modules.Searches -{ - internal class SearchesModule : DiscordModule - { - private readonly Random rng; - public SearchesModule() - { - commands.Add(new LoLCommands(this)); - commands.Add(new StreamNotifications(this)); - commands.Add(new ConverterCommand(this)); - commands.Add(new RedditCommand(this)); - commands.Add(new WowJokeCommand(this)); - commands.Add(new CalcCommand(this)); - commands.Add(new OsuCommands(this)); - commands.Add(new PokemonSearchCommands(this)); - commands.Add(new MemegenCommands(this)); - rng = new Random(); - } - - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Searches; - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand(Prefix + "we") - .Description($"Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | `{Prefix}we Moscow RF`") - .Parameter("city", ParameterType.Required) - .Parameter("country", ParameterType.Required) - .Do(async e => - { - var city = e.GetArg("city").Replace(" ", ""); - var country = e.GetArg("country").Replace(" ", ""); - var response = await SearchHelper.GetResponseStringAsync($"http://api.ninetales.us/nadekobot/weather/?city={city}&country={country}").ConfigureAwait(false); - - var obj = JObject.Parse(response)["weather"]; - - await e.Channel.SendMessage( -$@"🌍 **Weather for** 【{obj["target"]}】 -📏 **Lat,Long:** ({obj["latitude"]}, {obj["longitude"]}) ☁ **Condition:** {obj["condition"]} -😓 **Humidity:** {obj["humidity"]}% 💨 **Wind Speed:** {obj["windspeedk"]}km/h / {obj["windspeedm"]}mph -🔆 **Temperature:** {obj["centigrade"]}°C / {obj["fahrenheit"]}°F 🔆 **Feels like:** {obj["feelscentigrade"]}°C / {obj["feelsfahrenheit"]}°F -🌄 **Sunrise:** {obj["sunrise"]} 🌇 **Sunset:** {obj["sunset"]}").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "yt") - .Parameter("query", ParameterType.Unparsed) - .Description($"Searches youtubes and shows the first result | `{Prefix}yt query`") - .Do(async e => - { - if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; - var link = await SearchHelper.FindYoutubeUrlByKeywords(e.GetArg("query")).ConfigureAwait(false); - if (string.IsNullOrWhiteSpace(link)) - { - await e.Channel.SendMessage("No results found for that query."); - return; - } - var shortUrl = await SearchHelper.ShortenUrl(link).ConfigureAwait(false); - await e.Channel.SendMessage(shortUrl).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "ani") - .Alias(Prefix + "anime", Prefix + "aq") - .Parameter("query", ParameterType.Unparsed) - .Description($"Queries anilist for an anime and shows the first result. | `{Prefix}aq aquarion evol`") - .Do(async e => - { - if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; - string result; - try - { - result = (await SearchHelper.GetAnimeData(e.GetArg("query")).ConfigureAwait(false)).ToString(); - } - catch - { - await e.Channel.SendMessage("Failed to find that anime.").ConfigureAwait(false); - return; - } - - await e.Channel.SendMessage(result.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "imdb") - .Parameter("query", ParameterType.Unparsed) - .Description($"Queries imdb for movies or series, show first result. | `{Prefix}imdb Batman vs Superman`") - .Do(async e => - { - if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; - await e.Channel.SendIsTyping().ConfigureAwait(false); - string result; - try - { - var movie = ImdbScraper.ImdbScrape(e.GetArg("query"), true); - if (movie.Status) result = movie.ToString(); - else result = "Failed to find that movie."; - } - catch - { - await e.Channel.SendMessage("Failed to find that movie.").ConfigureAwait(false); - return; - } - - await e.Channel.SendMessage(result.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "mang") - .Alias(Prefix + "manga").Alias(Prefix + "mq") - .Parameter("query", ParameterType.Unparsed) - .Description($"Queries anilist for a manga and shows the first result. | `{Prefix}mq Shingeki no kyojin`") - .Do(async e => - { - if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; - string result; - try - { - result = (await SearchHelper.GetMangaData(e.GetArg("query")).ConfigureAwait(false)).ToString(); - } - catch - { - await e.Channel.SendMessage("Failed to find that anime.").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage(result).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "randomcat") - .Alias(Prefix + "meow") - .Description($"Shows a random cat image. | `{Prefix}meow`") - .Do(async e => - { - await e.Channel.SendMessage(JObject.Parse( - await SearchHelper.GetResponseStringAsync("http://www.random.cat/meow").ConfigureAwait(false))["file"].ToString()) - .ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "randomdog") - .Alias(Prefix + "woof") - .Description($"Shows a random dog image. | `{Prefix}woof`") - .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") - .Description($"Pulls the first image found using a search parameter. Use ~ir for different results. | `{Prefix}i cute kitten`") - .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("query"))) - return; - try - { - var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}"; - var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false)); - await e.Channel.SendMessage(obj["items"][0]["link"].ToString()).ConfigureAwait(false); - } - catch (HttpRequestException exception) - { - if (exception.Message.Contains("403 (Forbidden)")) - { - await e.Channel.SendMessage("Daily limit reached!"); - } - else - { - await e.Channel.SendMessage("Something went wrong."); - } - } - }); - - cgb.CreateCommand(Prefix + "ir") - .Description($"Pulls a random image using a search parameter. | `{Prefix}ir cute kitten`") - .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - if (string.IsNullOrWhiteSpace(e.GetArg("query"))) - return; - try - { - var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}"; - var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false)); - var items = obj["items"] as JArray; - await e.Channel.SendMessage(items[0]["link"].ToString()).ConfigureAwait(false); - } - catch (HttpRequestException exception) - { - if (exception.Message.Contains("403 (Forbidden)")) - { - await e.Channel.SendMessage("Daily limit reached!"); - } - else - { - await e.Channel.SendMessage("Something went wrong."); - } - } - }); - - cgb.CreateCommand(Prefix + "lmgtfy") - .Description($"Google something for an idiot. | `{Prefix}lmgtfy query`") - .Parameter("ffs", ParameterType.Unparsed) - .Do(async e => - { - if (e.GetArg("ffs") == null || e.GetArg("ffs").Length < 1) return; - await e.Channel.SendMessage(await $"http://lmgtfy.com/?q={ Uri.EscapeUriString(e.GetArg("ffs").ToString()) }".ShortenUrl()) - .ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "google") - .Alias(Prefix + "g") - .Description($"Get a google search link for some terms. | `{Prefix}google query`") - .Parameter("terms", ParameterType.Unparsed) - .Do(async e => - { - var terms = e.GetArg("terms")?.Trim(); - if (string.IsNullOrWhiteSpace(terms)) - return; - await e.Channel.SendMessage($"https://google.com/search?q={ HttpUtility.UrlEncode(terms).Replace(' ', '+') }") - .ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "hs") - .Description($"Searches for a Hearthstone card and shows its image. Takes a while to complete. | `{Prefix}hs Ysera`") - .Parameter("name", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("name"); - if (string.IsNullOrWhiteSpace(arg)) - { - await e.Channel.SendMessage("💢 Please enter a card name to search for.").ConfigureAwait(false); - return; - } - await e.Channel.SendIsTyping().ConfigureAwait(false); - var headers = new Dictionary { { "X-Mashape-Key", NadekoBot.Creds.MashapeKey } }; - var res = await SearchHelper.GetResponseStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}", headers) - .ConfigureAwait(false); - try - { - var items = JArray.Parse(res); - var images = new List(); - if (items == null) - throw new KeyNotFoundException("Cannot find a card by that name"); - var cnt = 0; - items.Shuffle(); - foreach (var item in items.TakeWhile(item => cnt++ < 4).Where(item => item.HasValues && item["img"] != null)) - { - images.Add( - Image.FromStream(await SearchHelper.GetResponseStreamAsync(item["img"].ToString()).ConfigureAwait(false))); - } - if (items.Count > 4) - { - await e.Channel.SendMessage("⚠ Found over 4 images. Showing random 4.").ConfigureAwait(false); - } - await e.Channel.SendFile(arg + ".png", (await images.MergeAsync()).ToStream(System.Drawing.Imaging.ImageFormat.Png)) - .ConfigureAwait(false); - } - catch (Exception ex) - { - await e.Channel.SendMessage($"💢 Error {ex.Message}").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "ud") - .Description($"Searches Urban Dictionary for a word. | `{Prefix}ud Pineapple`") - .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("query"); - if (string.IsNullOrWhiteSpace(arg)) - { - await e.Channel.SendMessage("💢 Please enter a search term.").ConfigureAwait(false); - return; - } - await e.Channel.SendIsTyping().ConfigureAwait(false); - var headers = new Dictionary { { "X-Mashape-Key", NadekoBot.Creds.MashapeKey } }; - var res = await SearchHelper.GetResponseStringAsync($"https://mashape-community-urban-dictionary.p.mashape.com/define?term={Uri.EscapeUriString(arg)}", headers).ConfigureAwait(false); - try - { - var items = JObject.Parse(res); - var sb = new System.Text.StringBuilder(); - var item = items["list"][0]; - sb.AppendLine($"`Term:` {item["word"].ToString()}"); - sb.AppendLine($"`Definition:` {item["definition"].ToString()}"); - sb.Append($"`Link:` <{item["permalink"].ToString()}>"); - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("💢 Failed finding a definition for that term.").ConfigureAwait(false); - } - }); - // thanks to Blaubeerwald - cgb.CreateCommand(Prefix + "#") - .Description($"Searches Tagdef.com for a hashtag. | `{Prefix}# ff`") - .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - var arg = e.GetArg("query"); - if (string.IsNullOrWhiteSpace(arg)) - { - await e.Channel.SendMessage("💢 Please enter a search term.").ConfigureAwait(false); - return; - } - await e.Channel.SendIsTyping().ConfigureAwait(false); - var headers = new Dictionary { { "X-Mashape-Key", NadekoBot.Creds.MashapeKey } }; - var res = await SearchHelper.GetResponseStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(arg)}.json", headers).ConfigureAwait(false); - try - { - var items = JObject.Parse(res); - var sb = new System.Text.StringBuilder(); - sb.AppendLine($"`Hashtag:` {items["defs"]["def"]["hashtag"].ToString()}"); - sb.AppendLine($"`Definition:` {items["defs"]["def"]["text"].ToString()}"); - sb.Append($"`Link:` <{await items["defs"]["def"]["uri"].ToString().ShortenUrl().ConfigureAwait(false)}>"); - await e.Channel.SendMessage(sb.ToString()); - } - catch - { - await e.Channel.SendMessage("💢 Failed finidng a definition for that tag.").ConfigureAwait(false); - } - }); - - cgb.CreateCommand(Prefix + "quote") - .Description($"Shows a random quote. | `{Prefix}quote`") - .Do(async e => - { - var quote = NadekoBot.Config.Quotes[rng.Next(0, NadekoBot.Config.Quotes.Count)].ToString(); - await e.Channel.SendMessage(quote).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "catfact") - .Description($"Shows a random catfact from | `{Prefix}catfact`") - .Do(async e => - { - var response = await SearchHelper.GetResponseStringAsync("http://catfacts-api.appspot.com/api/facts").ConfigureAwait(false); - if (response == null) - return; - await e.Channel.SendMessage($"🐈 `{JObject.Parse(response)["facts"][0].ToString()}`").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "yomama") - .Alias(Prefix + "ym") - .Description($"Shows a random joke from | `{Prefix}ym`") - .Do(async e => - { - var response = await SearchHelper.GetResponseStringAsync("http://api.yomomma.info/").ConfigureAwait(false); - await e.Channel.SendMessage("`" + JObject.Parse(response)["joke"].ToString() + "` 😆").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "randjoke") - .Alias(Prefix + "rj") - .Description($"Shows a random joke from | `{Prefix}rj`") - .Do(async e => - { - var response = await SearchHelper.GetResponseStringAsync("http://tambal.azurewebsites.net/joke/random").ConfigureAwait(false); - await e.Channel.SendMessage("`" + JObject.Parse(response)["joke"].ToString() + "` 😆").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "chucknorris") - .Alias(Prefix + "cn") - .Description($"Shows a random chucknorris joke from | `{Prefix}cn`") - .Do(async e => - { - var response = await SearchHelper.GetResponseStringAsync("http://api.icndb.com/jokes/random/").ConfigureAwait(false); - await e.Channel.SendMessage("`" + JObject.Parse(response)["value"]["joke"].ToString() + "` 😆").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "magicitem") - .Alias(Prefix + "mi") - .Description($"Shows a random magicitem from | `{Prefix}mi`") - .Do(async e => - { - var magicItems = JsonConvert.DeserializeObject>(File.ReadAllText("data/magicitems.json")); - var item = magicItems[rng.Next(0, magicItems.Count)].ToString(); - - await e.Channel.SendMessage(item).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "revav") - .Description($"Returns a google reverse image search for someone's avatar. | `{Prefix}revav \"@SomeGuy\"`") - .Parameter("user", ParameterType.Unparsed) - .Do(async e => - { - var usrStr = e.GetArg("user")?.Trim(); - - if (string.IsNullOrWhiteSpace(usrStr)) - return; - - var usr = e.Server.FindUsers(usrStr).FirstOrDefault(); - - if (usr == null || string.IsNullOrWhiteSpace(usr.AvatarUrl)) - return; - await e.Channel.SendMessage($"https://images.google.com/searchbyimage?image_url={usr.AvatarUrl}").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "revimg") - .Description($"Returns a google reverse image search for an image from a link. | `{Prefix}revav Image link`") - .Parameter("image", ParameterType.Unparsed) - .Do(async e => - { - var imgLink = e.GetArg("image")?.Trim(); - - if (string.IsNullOrWhiteSpace(imgLink)) - return; - await e.Channel.SendMessage($"https://images.google.com/searchbyimage?image_url={imgLink}").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "safebooru") - .Description($"Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | `{Prefix}safebooru yuri+kissing`") - .Parameter("tag", ParameterType.Unparsed) - .Do(async e => - { - var tag = e.GetArg("tag")?.Trim() ?? ""; - var link = await SearchHelper.GetSafebooruImageLink(tag).ConfigureAwait(false); - if (link == null) - await e.Channel.SendMessage("`No results.`"); - else - await e.Channel.SendMessage(link).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "wiki") - .Description($"Gives you back a wikipedia link | `{Prefix}wiki query`") - .Parameter("query", ParameterType.Unparsed) - .Do(async e => - { - var query = e.GetArg("query"); - var result = await SearchHelper.GetResponseStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query)); - var data = JsonConvert.DeserializeObject(result); - if (data.Query.Pages[0].Missing) - await e.Channel.SendMessage("`That page could not be found.`"); - else - await e.Channel.SendMessage(data.Query.Pages[0].FullUrl); - }); - - cgb.CreateCommand(Prefix + "clr") - .Description($"Shows you what color corresponds to that hex. | `{Prefix}clr 00ff00`") - .Parameter("color", ParameterType.Unparsed) - .Do(async e => - { - var arg1 = e.GetArg("color")?.Trim()?.Replace("#", ""); - if (string.IsNullOrWhiteSpace(arg1)) - return; - var img = new Bitmap(50, 50); - - var red = Convert.ToInt32(arg1.Substring(0, 2), 16); - var green = Convert.ToInt32(arg1.Substring(2, 2), 16); - var blue = Convert.ToInt32(arg1.Substring(4, 2), 16); - var brush = new SolidBrush(System.Drawing.Color.FromArgb(red, green, blue)); - - using (Graphics g = Graphics.FromImage(img)) - { - g.FillRectangle(brush, 0, 0, 50, 50); - g.Flush(); - } - - await e.Channel.SendFile("arg1.png", img.ToStream()); - }); - - - cgb.CreateCommand(Prefix + "videocall") - .Description($"Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `{Prefix}videocall \"@SomeGuy\"`") - .Parameter("arg", ParameterType.Unparsed) - .Do(async e => - { - try - { - var allUsrs = e.Message.MentionedUsers.Union(new User[] { e.User }); - var allUsrsArray = allUsrs as User[] ?? allUsrs.ToArray(); - var str = allUsrsArray.Aggregate("http://appear.in/", (current, usr) => current + Uri.EscapeUriString(usr.Name[0].ToString())); - str += new Random().Next(); - foreach (var usr in allUsrsArray) - { - await usr.SendMessage(str).ConfigureAwait(false); - } - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - }); - - cgb.CreateCommand(Prefix + "av") - .Alias(Prefix + "avatar") - .Parameter("mention", ParameterType.Required) - .Description($"Shows a mentioned person's avatar. | `{Prefix}av \"@SomeGuy\"`") - .Do(async e => - { - var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("Invalid user specified.").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage(await usr.AvatarUrl.ShortenUrl()).ConfigureAwait(false); - }); - - }); - } - } -} - diff --git a/NadekoBot/Modules/Translator/TranslateCommand.cs b/NadekoBot/Modules/Translator/TranslateCommand.cs deleted file mode 100644 index e54aa020..00000000 --- a/NadekoBot/Modules/Translator/TranslateCommand.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Translator.Helpers; -using System; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Translator -{ - class TranslateCommand : DiscordCommand - { - public TranslateCommand(DiscordModule module) : base(module) { } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "translate") - .Alias(Module.Prefix + "trans") - .Description($"Translates from>to text. From the given language to the destiation language. | `{Module.Prefix}trans en>fr Hello`") - .Parameter("langs", ParameterType.Required) - .Parameter("text", ParameterType.Unparsed) - .Do(TranslateFunc()); - } - private GoogleTranslator t = new GoogleTranslator(); - private Func TranslateFunc() => async e => - { - try - { - await e.Channel.SendIsTyping().ConfigureAwait(false); - string from = e.GetArg("langs").ToLowerInvariant().Split('>')[0]; - string to = e.GetArg("langs").ToLowerInvariant().Split('>')[1]; - var text = e.GetArg("text")?.Trim(); - if (string.IsNullOrWhiteSpace(text)) - return; - - string translation = await t.Translate(text, from, to).ConfigureAwait(false); - await e.Channel.SendMessage(translation).ConfigureAwait(false); - } - catch (Exception ex) - { - Console.WriteLine(ex); - await e.Channel.SendMessage("Bad input format, or something went wrong...").ConfigureAwait(false); - } - - }; - } -} diff --git a/NadekoBot/Modules/Translator/TranslatorModule.cs b/NadekoBot/Modules/Translator/TranslatorModule.cs deleted file mode 100644 index 1b333a6b..00000000 --- a/NadekoBot/Modules/Translator/TranslatorModule.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Discord.Modules; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; - -namespace NadekoBot.Modules.Translator -{ - internal class TranslatorModule : DiscordModule - { - public TranslatorModule() - { - commands.Add(new TranslateCommand(this)); - commands.Add(new ValidLanguagesCommand(this)); - } - - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Searches; - - public override void Install(ModuleManager manager) - { - manager.CreateCommands("", cgb => - { - cgb.AddCheck(PermissionChecker.Instance); - commands.ForEach(cmd => cmd.Init(cgb)); - }); - } - - } -} diff --git a/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs b/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs deleted file mode 100644 index 438e77de..00000000 --- a/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Translator.Helpers; -using System; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Translator -{ - class ValidLanguagesCommand : DiscordCommand - { - public ValidLanguagesCommand(DiscordModule module) : base(module) { } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "translangs") - .Description($"List the valid languages for translation. | `{Prefix}translangs` or `{Prefix}translangs language`") - .Parameter("search", ParameterType.Optional) - .Do(ListLanguagesFunc()); - } - private Func ListLanguagesFunc() => async e => - { - try - { - GoogleTranslator.EnsureInitialized(); - string s = e.GetArg("search"); - string ret = ""; - foreach (string key in GoogleTranslator._languageModeMap.Keys) - { - if (!s.Equals("")) - { - if (key.ToLower().Contains(s)) - { - ret += " " + key + ";"; - } - } - else - { - ret += " " + key + ";"; - } - } - await e.Channel.SendMessage(ret).ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Bad input format, or sth went wrong...").ConfigureAwait(false); - } - - }; - } -} diff --git a/NadekoBot/Modules/Trello/TrelloModule.cs b/NadekoBot/Modules/Trello/TrelloModule.cs deleted file mode 100644 index 176ca2b8..00000000 --- a/NadekoBot/Modules/Trello/TrelloModule.cs +++ /dev/null @@ -1,145 +0,0 @@ -using Discord.Modules; -using Manatee.Trello; -using Manatee.Trello.ManateeJson; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Timers; -using Action = Manatee.Trello.Action; - -namespace NadekoBot.Modules.Trello -{ - internal class TrelloModule : DiscordModule - { - private readonly Timer t = new Timer { Interval = 2000 }; - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Trello; - - public override void Install(ModuleManager manager) - { - - var client = manager.Client; - - var serializer = new ManateeSerializer(); - TrelloConfiguration.Serializer = serializer; - TrelloConfiguration.Deserializer = serializer; - TrelloConfiguration.JsonFactory = new ManateeFactory(); - TrelloConfiguration.RestClientProvider = new Manatee.Trello.WebApi.WebApiClientProvider(); - TrelloAuthorization.Default.AppKey = NadekoBot.Creds.TrelloAppKey; - //TrelloAuthorization.Default.UserToken = "[your user token]"; - - Discord.Channel bound = null; - Board board = null; - - List last5ActionIDs = null; - t.Elapsed += async (s, e) => - { - try - { - if (board == null || bound == null) - return; //do nothing if there is no bound board - - board.Refresh(); - var cur5Actions = board.Actions.Take(board.Actions.Count() < 5 ? board.Actions.Count() : 5); - var cur5ActionsArray = cur5Actions as Action[] ?? cur5Actions.ToArray(); - - if (last5ActionIDs == null) - { - last5ActionIDs = cur5ActionsArray.Select(a => a.Id).ToList(); - return; - } - - foreach (var a in cur5ActionsArray.Where(ca => !last5ActionIDs.Contains(ca.Id))) - { - await bound.Send("**--TRELLO NOTIFICATION--**\n" + a.ToString()).ConfigureAwait(false); - } - last5ActionIDs.Clear(); - last5ActionIDs.AddRange(cur5ActionsArray.Select(a => a.Id)); - } - catch (Exception ex) - { - Console.WriteLine("Timer failed " + ex.ToString()); - } - }; - - manager.CreateCommands("", cgb => - { - - cgb.AddCheck(PermissionChecker.Instance); - - cgb.CreateCommand(Prefix + "bind") - .Description("Bind a trello bot to a single channel. " + - "You will receive notifications from your board when something is added or edited." + - $" **Bot Owner Only!**| `{Prefix}bind [board_id]`") - .Parameter("board_id", Discord.Commands.ParameterType.Required) - .Do(async e => - { - if (!NadekoBot.IsOwner(e.User.Id)) return; - if (bound != null) return; - try - { - bound = e.Channel; - board = new Board(e.GetArg("board_id").Trim()); - board.Refresh(); - await e.Channel.SendMessage("Successfully bound to this channel and board " + board.Name); - t.Start(); - } - catch (Exception ex) - { - Console.WriteLine("Failed to join the board. " + ex.ToString()); - } - }); - - cgb.CreateCommand(Prefix + "unbind") - .Description($"Unbinds a bot from the channel and board. **Bot Owner Only!**| `{Prefix}unbind`") - .Do(async e => - { - if (!NadekoBot.IsOwner(e.User.Id)) return; - if (bound == null || bound != e.Channel) return; - t.Stop(); - bound = null; - board = null; - await e.Channel.SendMessage("Successfully unbound trello from this channel.").ConfigureAwait(false); - - }); - - cgb.CreateCommand(Prefix + "lists") - .Alias(Prefix + "list") - .Description($"Lists all lists, yo ;) **Bot Owner Only!**| `{Prefix}list`") - .Do(async e => - { - if (!NadekoBot.IsOwner(e.User.Id)) return; - if (bound == null || board == null || bound != e.Channel) return; - await e.Channel.SendMessage("Lists for a board '" + board.Name + "'\n" + string.Join("\n", board.Lists.Select(l => "**• " + l.ToString() + "**"))) - .ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "cards") - .Description($"Lists all cards from the supplied list. You can supply either a name or an index. **Bot Owner Only!**| `{Prefix}cards index`") - .Parameter("list_name", Discord.Commands.ParameterType.Unparsed) - .Do(async e => - { - if (!NadekoBot.IsOwner(e.User.Id)) return; - if (bound == null || board == null || bound != e.Channel || e.GetArg("list_name") == null) return; - - int num; - var success = int.TryParse(e.GetArg("list_name"), out num); - List list = null; - if (success && num <= board.Lists.Count() && num > 0) - list = board.Lists[num - 1]; - else - list = board.Lists.FirstOrDefault(l => l.Name == e.GetArg("list_name")); - - - if (list != null) - await e.Channel.SendMessage("There are " + list.Cards.Count() + " cards in a **" + list.Name + "** list\n" + string.Join("\n", list.Cards.Select(c => "**• " + c.ToString() + "**"))) - .ConfigureAwait(false); - else - await e.Channel.SendMessage("No such list.") - .ConfigureAwait(false); - }); - }); - } - } -} diff --git a/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/NadekoBot/Modules/Utility/Commands/InfoCommands.cs deleted file mode 100644 index 4a19b990..00000000 --- a/NadekoBot/Modules/Utility/Commands/InfoCommands.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using System; -using System.Linq; -using System.Text; - -namespace NadekoBot.Modules.Utility.Commands -{ - class InfoCommands : DiscordCommand - { - public InfoCommands(DiscordModule module) : base(module) - { - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "serverinfo") - .Alias(Module.Prefix + "sinfo") - .Description($"Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. |`{Module.Prefix}sinfo Some Server`") - .Parameter("server", ParameterType.Optional) - .Do(async e => - { - var servText = e.GetArg("server")?.Trim(); - var server = string.IsNullOrWhiteSpace(servText) - ? e.Server - : NadekoBot.Client.FindServers(servText).FirstOrDefault(); - if (server == null) - return; - var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(server.Id >> 22); - var sb = new StringBuilder(); - sb.AppendLine($"`Name:` **#{server.Name}**"); - sb.AppendLine($"`Owner:` **{server.Owner}**"); - sb.AppendLine($"`Id:` **{server.Id}**"); - sb.AppendLine($"`Icon Url:` **{await server.IconUrl.ShortenUrl().ConfigureAwait(false)}**"); - sb.AppendLine($"`TextChannels:` **{server.TextChannels.Count()}** `VoiceChannels:` **{server.VoiceChannels.Count()}**"); - sb.AppendLine($"`Members:` **{server.UserCount}** `Online:` **{server.Users.Count(u => u.Status == UserStatus.Online)}** (may be incorrect)"); - sb.AppendLine($"`Roles:` **{server.Roles.Count()}**"); - sb.AppendLine($"`Created At:` **{createdAt}**"); - if (server.CustomEmojis.Count() > 0) - sb.AppendLine($"`Custom Emojis:` **{string.Join(", ", server.CustomEmojis.Select(em => em.Name))}**"); - if (server.Features.Count() > 0) - sb.AppendLine($"`Features:` **{string.Join(", ", server.Features)}**"); - if (!string.IsNullOrWhiteSpace(server.SplashId)) - sb.AppendLine($"`Region:` **{server.Region.Name}**"); - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "channelinfo") - .Alias(Module.Prefix + "cinfo") - .Description($"Shows info about the channel. If no channel is supplied, it defaults to current one. |`{Module.Prefix}cinfo #some-channel`") - .Parameter("channel", ParameterType.Optional) - .Do(async e => - { - var chText = e.GetArg("channel")?.Trim(); - var ch = string.IsNullOrWhiteSpace(chText) - ? e.Channel - : e.Server.FindChannels(chText, Discord.ChannelType.Text).FirstOrDefault(); - if (ch == null) - return; - var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ch.Id >> 22); - var sb = new StringBuilder(); - sb.AppendLine($"`Name:` **#{ch.Name}**"); - sb.AppendLine($"`Id:` **{ch.Id}**"); - sb.AppendLine($"`Created At:` **{createdAt}**"); - sb.AppendLine($"`Topic:` **{ch.Topic}**"); - sb.AppendLine($"`Users:` **{ch.Users.Count()}**"); - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Module.Prefix + "userinfo") - .Alias(Module.Prefix + "uinfo") - .Description($"Shows info about the user. If no user is supplied, it defaults a user running the command. |`{Module.Prefix}uinfo @SomeUser`") - .Parameter("user", ParameterType.Optional) - .Do(async e => - { - var userText = e.GetArg("user")?.Trim(); - var user = string.IsNullOrWhiteSpace(userText) - ? e.User - : e.Server.FindUsers(userText).FirstOrDefault(); - if (user == null) - return; - var sb = new StringBuilder(); - sb.AppendLine($"`Name#Discrim:` **#{user.Name}#{user.Discriminator}**"); - if (!string.IsNullOrWhiteSpace(user.Nickname)) - sb.AppendLine($"`Nickname:` **{user.Nickname}**"); - sb.AppendLine($"`Id:` **{user.Id}**"); - sb.AppendLine($"`Current Game:` **{(user.CurrentGame?.Name == null ? "-" : user.CurrentGame.Value.Name)}**"); - if (user.LastOnlineAt != null) - sb.AppendLine($"`Last Online:` **{user.LastOnlineAt:HH:mm:ss}**"); - sb.AppendLine($"`Joined At:` **{user.JoinedAt}**"); - sb.AppendLine($"`Roles:` **({user.Roles.Count()}) - {string.Join(", ", user.Roles.Select(r => r.Name))}**"); - sb.AppendLine($"`AvatarUrl:` **{await user.AvatarUrl.ShortenUrl().ConfigureAwait(false)}**"); - await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); - }); - } - } -} diff --git a/NadekoBot/Modules/Utility/Commands/Remind.cs b/NadekoBot/Modules/Utility/Commands/Remind.cs deleted file mode 100644 index 07054394..00000000 --- a/NadekoBot/Modules/Utility/Commands/Remind.cs +++ /dev/null @@ -1,197 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.DataModels; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; -using System.Timers; - -namespace NadekoBot.Modules.Utility.Commands -{ - class Remind : DiscordCommand - { - - Regex regex = new Regex(@"^(?:(?\d)mo)?(?:(?\d)w)?(?:(?\d{1,2})d)?(?:(?\d{1,2})h)?(?:(?\d{1,2})m)?$", - RegexOptions.Compiled | RegexOptions.Multiline); - - List reminders = new List(); - - IDictionary> replacements = new Dictionary> - { - { "%message%" , (r) => r.Message }, - { "%user%", (r) => $"<@!{r.UserId}>" }, - { "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"} - }; - - public Remind(DiscordModule module) : base(module) - { - var remList = DbHandler.Instance.GetAllRows(); - - NadekoBot.OnReady += () => reminders = remList.Select(StartNewReminder).ToList(); - } - - private Timer StartNewReminder(Reminder r) - { - var now = DateTime.Now; - var twoMins = new TimeSpan(0, 2, 0); - TimeSpan time = (r.When - now) < twoMins - ? twoMins //if the time is less than 2 minutes, - : r.When - now; //it will send the message 2 minutes after start - //To account for high bot startup times - if (time.TotalMilliseconds > int.MaxValue) - return null; - var t = new Timer(time.TotalMilliseconds); - t.Elapsed += async (s, e) => - { - try - { - Channel ch; - if (r.IsPrivate) - { - ch = NadekoBot.Client.PrivateChannels.FirstOrDefault(c => (long)c.Id == r.ChannelId); - if (ch == null) - ch = await NadekoBot.Client.CreatePrivateChannel((ulong)r.ChannelId).ConfigureAwait(false); - } - else - ch = NadekoBot.Client.GetServer((ulong)r.ServerId)?.GetChannel((ulong)r.ChannelId); - - if (ch == null) - return; - - await ch.SendMessage( - replacements.Aggregate(NadekoBot.Config.RemindMessageFormat, - (cur, replace) => cur.Replace(replace.Key, replace.Value(r))) - ).ConfigureAwait(false); //it works trust me - - } - catch (Exception ex) - { - Console.WriteLine($"Timer error! {ex}"); - } - finally - { - DbHandler.Instance.Delete(r.Id.Value); - t.Stop(); - t.Dispose(); - } - }; - t.Start(); - return t; - } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "remind") - .Description("Sends a message to you or a channel after certain amount of time. " + - "First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. " + - "Third argument is a (multiword)message. " + - $" | `{Prefix}remind me 1d5h Do something` or `{Prefix}remind #general Start now!`") - .Parameter("meorchannel", ParameterType.Required) - .Parameter("time", ParameterType.Required) - .Parameter("message", ParameterType.Unparsed) - .Do(async e => - { - var meorchStr = e.GetArg("meorchannel").ToUpperInvariant(); - Channel ch; - bool isPrivate = false; - if (meorchStr == "ME") - { - isPrivate = true; - ch = await e.User.CreatePMChannel().ConfigureAwait(false); - } - else if (meorchStr == "HERE") - { - ch = e.Channel; - } - else - { - ch = e.Server.FindChannels(meorchStr).FirstOrDefault(); - } - - if (ch == null) - { - await e.Channel.SendMessage($"{e.User.Mention} Something went wrong (channel cannot be found) ;(").ConfigureAwait(false); - return; - } - - var timeStr = e.GetArg("time"); - - var m = regex.Match(timeStr); - - if (m.Length == 0) - { - await e.Channel.SendMessage("Not a valid time format blablabla").ConfigureAwait(false); - return; - } - - string output = ""; - var namesAndValues = new Dictionary(); - - foreach (var groupName in regex.GetGroupNames()) - { - if (groupName == "0") continue; - int value = 0; - int.TryParse(m.Groups[groupName].Value, out value); - - if (string.IsNullOrEmpty(m.Groups[groupName].Value)) - { - namesAndValues[groupName] = 0; - continue; - } - else if (value < 1 || - (groupName == "months" && value > 1) || - (groupName == "weeks" && value > 4) || - (groupName == "days" && value >= 7) || - (groupName == "hours" && value > 23) || - (groupName == "minutes" && value > 59)) - { - await e.Channel.SendMessage($"Invalid {groupName} value.").ConfigureAwait(false); - return; - } - else - namesAndValues[groupName] = value; - output += m.Groups[groupName].Value + " " + groupName + " "; - } - var time = DateTime.Now + new TimeSpan(30 * namesAndValues["months"] + - 7 * namesAndValues["weeks"] + - namesAndValues["days"], - namesAndValues["hours"], - namesAndValues["minutes"], - 0); - - var rem = new Reminder - { - ChannelId = (long)ch.Id, - IsPrivate = isPrivate, - When = time, - Message = e.GetArg("message"), - UserId = (long)e.User.Id, - ServerId = (long)e.Server.Id - }; - DbHandler.Instance.Connection.Insert(rem); - - reminders.Add(StartNewReminder(rem)); - - await e.Channel.SendMessage($"⏰ I will remind \"{ch.Name}\" to \"{e.GetArg("message").ToString()}\" in {output}. ({time:d.M.yyyy.} at {time:HH:mm})").ConfigureAwait(false); - }); - cgb.CreateCommand(Module.Prefix + "remindmsg") - .Description("Sets message for when the remind is triggered. " + - " Available placeholders are %user% - user who ran the command, %message% -" + - $" Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!** | `{Prefix}remindmsg do something else`") - .Parameter("msg", ParameterType.Unparsed) - .AddCheck(SimpleCheckers.OwnerOnly()) - .Do(async e => - { - var arg = e.GetArg("msg")?.Trim(); - if (string.IsNullOrWhiteSpace(arg)) - return; - - NadekoBot.Config.RemindMessageFormat = arg; - await e.Channel.SendMessage("`New remind message set.`"); - }); - } - } -} diff --git a/NadekoBot/Modules/Utility/UtilityModule.cs b/NadekoBot/Modules/Utility/UtilityModule.cs deleted file mode 100644 index 218ccbae..00000000 --- a/NadekoBot/Modules/Utility/UtilityModule.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using NadekoBot.Modules.Utility.Commands; -using System; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Utility -{ - internal class UtilityModule : DiscordModule - { - public UtilityModule() - { - commands.Add(new Remind(this)); - commands.Add(new InfoCommands(this)); - } - - public override string Prefix => NadekoBot.Config.CommandPrefixes.Utility; - - public override void Install(ModuleManager manager) - { - - manager.CreateCommands("", cgb => - { - cgb.AddCheck(PermissionChecker.Instance); - - var client = manager.Client; - - commands.ForEach(cmd => cmd.Init(cgb)); - - cgb.CreateCommand(Prefix + "whoplays") - .Description($"Shows a list of users who are playing the specified game. | `{Prefix}whoplays Overwatch`") - .Parameter("game", ParameterType.Unparsed) - .Do(async e => - { - var game = e.GetArg("game")?.Trim().ToUpperInvariant(); - if (string.IsNullOrWhiteSpace(game)) - return; - var en = e.Server.Users - .Where(u => u.CurrentGame?.Name?.ToUpperInvariant() == game) - .Select(u => u.Name); - - var arr = en as string[] ?? en.ToArray(); - - int i = 0; - if (arr.Length == 0) - await e.Channel.SendMessage("Nobody. (not 100% sure)").ConfigureAwait(false); - else - await e.Channel.SendMessage("```xl\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Concat(ig.Select(el => $"• {el,-35}")))) + "\n```").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "inrole") - .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) - .Do(async e => - { - await Task.Run(async () => - { - var arg = e.GetArg("roles").Split(',').Select(r => r.Trim()); - string send = $"`Here is a list of users in a specfic role:`"; - foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str) && str != "@everyone" && str != "everyone")) - { - var role = e.Server.FindRoles(roleStr).FirstOrDefault(); - if (role == null) continue; - send += $"\n`{role.Name}`\n"; - send += string.Join(", ", role.Members.Select(r => "**" + r.Name + "**#" + r.Discriminator)); - } - - 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); - await - e.Channel.Send(curstr.Substring(0, - curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1)).ConfigureAwait(false); - send = curstr.Substring(curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1) + - send.Substring(2000); - } - await e.Channel.Send(send).ConfigureAwait(false); - }).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "checkmyperms") - .Description($"Checks your userspecific permissions on this channel. | `{Prefix}checkmyperms`") - .Do(async e => - { - var output = "```\n"; - foreach (var p in e.User.ServerPermissions.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any())) - { - output += p.Name + ": " + p.GetValue(e.User.ServerPermissions, null).ToString() + "\n"; - } - output += "```"; - await e.User.SendMessage(output).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "stats") - .Description($"Shows some basic stats for Nadeko. | `{Prefix}stats`") - .Do(async e => - { - await e.Channel.SendMessage(await NadekoStats.Instance.GetStats()).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "dysyd") - .Description($"Shows some basic stats for Nadeko. | `{Prefix}dysyd`") - .Do(async e => - { - await e.Channel.SendMessage((await NadekoStats.Instance.GetStats()).Matrix().TrimTo(1990)).ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "userid").Alias(Prefix + "uid") - .Description($"Shows user ID. | `{Prefix}uid` or `{Prefix}uid \"@SomeGuy\"`") - .Parameter("user", ParameterType.Unparsed) - .Do(async e => - { - var usr = e.User; - if (!string.IsNullOrWhiteSpace(e.GetArg("user"))) usr = e.Channel.FindUsers(e.GetArg("user")).FirstOrDefault(); - if (usr == null) - return; - await e.Channel.SendMessage($"Id of the user { usr.Name } is { usr.Id }").ConfigureAwait(false); - }); - - cgb.CreateCommand(Prefix + "channelid").Alias(Prefix + "cid") - .Description($"Shows current channel ID. | `{Prefix}cid`") - .Do(async e => await e.Channel.SendMessage("This channel's ID is " + e.Channel.Id).ConfigureAwait(false)); - - cgb.CreateCommand(Prefix + "serverid").Alias(Prefix + "sid") - .Description($"Shows current server ID. | `{Prefix}sid`") - .Do(async e => await e.Channel.SendMessage("This server's ID is " + e.Server.Id).ConfigureAwait(false)); - - cgb.CreateCommand(Prefix + "roles") - .Description($"List all roles on this server or a single user if specified. | `{Prefix}roles`") - .Parameter("user", ParameterType.Unparsed) - .Do(async e => - { - if (!string.IsNullOrWhiteSpace(e.GetArg("user"))) - { - var usr = e.Server.FindUsers(e.GetArg("user")).FirstOrDefault(); - if (usr == null) return; - - await e.Channel.SendMessage($"`List of roles for **{usr.Name}**:` \n• " + string.Join("\n• ", usr.Roles)).ConfigureAwait(false); - return; - } - 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); - }); - }); - } - } -} - diff --git a/NadekoBot/NadekoBot.cs b/NadekoBot/NadekoBot.cs deleted file mode 100644 index 41bbada1..00000000 --- a/NadekoBot/NadekoBot.cs +++ /dev/null @@ -1,301 +0,0 @@ -using Discord; -using Discord.Audio; -using Discord.Commands; -using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.Classes.Help.Commands; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Modules.Administration; -using NadekoBot.Modules.ClashOfClans; -using NadekoBot.Modules.Conversations; -using NadekoBot.Modules.CustomReactions; -using NadekoBot.Modules.Gambling; -using NadekoBot.Modules.Games; -using NadekoBot.Modules.Games.Commands; -using NadekoBot.Modules.Help; -#if !NADEKO_RELEASE -using NadekoBot.Modules.Music; -#endif -using NadekoBot.Modules.NSFW; -using NadekoBot.Modules.Permissions; -using NadekoBot.Modules.Permissions.Classes; -using NadekoBot.Modules.Pokemon; -using NadekoBot.Modules.Searches; -using NadekoBot.Modules.Translator; -using NadekoBot.Modules.Trello; -using NadekoBot.Modules.Utility; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot -{ - public class NadekoBot - { - public static DiscordClient Client { get; private set; } - public static Credentials Creds { get; set; } - public static Configuration Config { get; set; } - public static LocalizedStrings Locale { get; set; } = new LocalizedStrings(); - public static string BotMention { get; set; } = ""; - public static bool Ready { get; set; } = false; - public static Action OnReady { get; set; } = delegate { }; - - private static List OwnerPrivateChannels { get; set; } - - private static void Main() - { - Console.OutputEncoding = Encoding.Unicode; - - try - { - File.WriteAllText("data/config_example.json", JsonConvert.SerializeObject(new Configuration(), Formatting.Indented)); - if (!File.Exists("data/config.json")) - File.Copy("data/config_example.json", "data/config.json"); - File.WriteAllText("credentials_example.json", JsonConvert.SerializeObject(new Credentials(), Formatting.Indented)); - - } - catch - { - Console.WriteLine("Failed writing credentials_example.json or data/config_example.json"); - } - - try - { - Config = JsonConvert.DeserializeObject(File.ReadAllText("data/config.json")); - Config.Quotes = JsonConvert.DeserializeObject>(File.ReadAllText("data/quotes.json")); - Config.PokemonTypes = JsonConvert.DeserializeObject>(File.ReadAllText("data/PokemonTypes.json")); - } - catch (Exception ex) - { - Console.WriteLine("Failed loading configuration."); - Console.WriteLine(ex); - Console.ReadKey(); - return; - } - - try - { - //load credentials from credentials.json - Creds = JsonConvert.DeserializeObject(File.ReadAllText("credentials.json")); - } - catch (Exception ex) - { - Console.WriteLine($"Failed to load stuff from credentials.json, RTFM\n{ex.Message}"); - Console.ReadKey(); - return; - } - - //if password is not entered, prompt for password - if (string.IsNullOrWhiteSpace(Creds.Token)) - { - Console.WriteLine("Token blank. Please enter your bot's token:\n"); - Creds.Token = Console.ReadLine(); - } - - Console.WriteLine(string.IsNullOrWhiteSpace(Creds.GoogleAPIKey) - ? "No google api key found. You will not be able to use music and links won't be shortened." - : "Google API key provided."); - Console.WriteLine(string.IsNullOrWhiteSpace(Creds.TrelloAppKey) - ? "No trello appkey found. You will not be able to use trello commands." - : "Trello app key provided."); - Console.WriteLine(Config.ForwardMessages != true - ? "Not forwarding messages." - : "Forwarding private messages to owner."); - Console.WriteLine(string.IsNullOrWhiteSpace(Creds.SoundCloudClientID) - ? "No soundcloud Client ID found. Soundcloud streaming is disabled." - : "SoundCloud streaming enabled."); - Console.WriteLine(string.IsNullOrWhiteSpace(Creds.OsuAPIKey) - ? "No osu! api key found. Song & top score lookups will not work. User lookups still available." - : "osu! API key provided."); - - BotMention = $"<@{Creds.BotId}>"; - - //create new discord client and log - Client = new DiscordClient(new DiscordConfigBuilder() - { - MessageCacheSize = 10, - ConnectionTimeout = int.MaxValue, - LogLevel = LogSeverity.Warning, - LogHandler = (s, e) => - Console.WriteLine($"Severity: {e.Severity}" + - $"ExceptionMessage: {e.Exception?.Message ?? "-"}" + - $"Message: {e.Message}"), - }); - - //create a command service - var commandService = new CommandService(new CommandServiceConfigBuilder - { - AllowMentionPrefix = false, - CustomPrefixHandler = m => 0, - HelpMode = HelpMode.Disabled, - ErrorHandler = async (s, e) => - { - if (e.ErrorType != CommandErrorType.BadPermissions) - return; - if (string.IsNullOrWhiteSpace(e.Exception?.Message)) - return; - try - { - await e.Channel.SendMessage(e.Exception.Message).ConfigureAwait(false); - } - catch { } - } - }); - - //add command service - Client.AddService(commandService); - - //create module service - var modules = Client.AddService(new ModuleService()); - - //add audio service - Client.AddService(new AudioService(new AudioServiceConfigBuilder() - { - Channels = 2, - EnableEncryption = false, - Bitrate = 128, - })); - - //install modules - modules.Add(new HelpModule(), "Help", ModuleFilter.None); - modules.Add(new AdministrationModule(), "Administration", ModuleFilter.None); - modules.Add(new UtilityModule(), "Utility", ModuleFilter.None); - modules.Add(new PermissionModule(), "Permissions", ModuleFilter.None); - modules.Add(new Conversations(), "Conversations", ModuleFilter.None); - modules.Add(new GamblingModule(), "Gambling", ModuleFilter.None); - modules.Add(new GamesModule(), "Games", ModuleFilter.None); -#if !NADEKO_RELEASE - modules.Add(new MusicModule(), "Music", ModuleFilter.None); -#endif - modules.Add(new SearchesModule(), "Searches", ModuleFilter.None); - modules.Add(new NSFWModule(), "NSFW", ModuleFilter.None); - modules.Add(new ClashOfClansModule(), "ClashOfClans", ModuleFilter.None); - modules.Add(new PokemonModule(), "Pokegame", ModuleFilter.None); - modules.Add(new TranslatorModule(), "Translator", ModuleFilter.None); - modules.Add(new CustomReactionsModule(), "Customreactions", ModuleFilter.None); - if (!string.IsNullOrWhiteSpace(Creds.TrelloAppKey)) - modules.Add(new TrelloModule(), "Trello", ModuleFilter.None); - - //run the bot - Client.ExecuteAndWait(async () => - { - await Task.Run(() => - { - Console.WriteLine("Specific config started initializing."); - var x = SpecificConfigurations.Default; - Console.WriteLine("Specific config done initializing."); - }); - - await PermissionsHandler.Initialize(); - - try - { - await Client.Connect(Creds.Token, TokenType.Bot).ConfigureAwait(false); - } - catch (Exception ex) - { - Console.WriteLine($"Token is wrong. Don't set a token if you don't have an official BOT account."); - Console.WriteLine(ex); - Console.ReadKey(); - return; - } -#if NADEKO_RELEASE - await Task.Delay(300000).ConfigureAwait(false); -#else - await Task.Delay(1000).ConfigureAwait(false); -#endif - - Console.WriteLine("-----------------"); - Console.WriteLine(await NadekoStats.Instance.GetStats().ConfigureAwait(false)); - Console.WriteLine("-----------------"); - - - OwnerPrivateChannels = new List(Creds.OwnerIds.Length); - foreach (var id in Creds.OwnerIds) - { - try - { - OwnerPrivateChannels.Add(await Client.CreatePrivateChannel(id).ConfigureAwait(false)); - } - catch - { - Console.WriteLine($"Failed creating private channel with the owner {id} listed in credentials.json"); - } - } - Client.ClientAPI.SendingRequest += (s, e) => - { - var request = e.Request as Discord.API.Client.Rest.SendMessageRequest; - if (request == null) return; - // meew0 is magic - request.Content = request.Content?.Replace("@everyone", "@everyοne").Replace("@here", "@һere") ?? "_error_"; - if (string.IsNullOrWhiteSpace(request.Content)) - e.Cancel = true; - }; -#if NADEKO_RELEASE - Client.ClientAPI.SentRequest += (s, e) => - { - Console.WriteLine($"[Request of type {e.Request.GetType()} sent in {e.Milliseconds}]"); - - var request = e.Request as Discord.API.Client.Rest.SendMessageRequest; - if (request == null) return; - - Console.WriteLine($"[Content: { request.Content }"); - }; -#endif - NadekoBot.Ready = true; - NadekoBot.OnReady(); - Console.WriteLine("Ready!"); - //reply to personal messages and forward if enabled. - Client.MessageReceived += Client_MessageReceived; - }); - Console.WriteLine("Exiting..."); - Console.ReadKey(); - } - - public static bool IsOwner(ulong id) => Creds.OwnerIds.Contains(id); - - public static async Task SendMessageToOwner(string message) - { - if (Config.ForwardMessages && OwnerPrivateChannels.Any()) - if (Config.ForwardToAllOwners) - OwnerPrivateChannels.ForEach(async c => - { - try { await c.SendMessage(message).ConfigureAwait(false); } catch { } - }); - else - { - var c = OwnerPrivateChannels.FirstOrDefault(); - if (c != null) - await c.SendMessage(message).ConfigureAwait(false); - } - } - - private static bool repliedRecently = false; - private static async void Client_MessageReceived(object sender, MessageEventArgs e) - { - try - { - if (e.Server != null || e.User.Id == Client.CurrentUser.Id) return; - if (PollCommand.ActivePolls.SelectMany(kvp => kvp.Key.Users.Select(u => u.Id)).Contains(e.User.Id)) return; - if (ConfigHandler.IsBlackListed(e)) - return; - - if (Config.ForwardMessages && !NadekoBot.Creds.OwnerIds.Contains(e.User.Id) && OwnerPrivateChannels.Any()) - await SendMessageToOwner(e.User + ": ```\n" + e.Message.Text + "\n```").ConfigureAwait(false); - - if (repliedRecently) return; - - repliedRecently = true; - if (e.Message.RawText != NadekoBot.Config.CommandPrefixes.Help + "h") - await e.Channel.SendMessage(HelpCommand.DMHelpString).ConfigureAwait(false); - await Task.Delay(2000).ConfigureAwait(false); - repliedRecently = false; - } - catch { } - } - } -} diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj deleted file mode 100644 index ae5e378e..00000000 --- a/NadekoBot/NadekoBot.csproj +++ /dev/null @@ -1,568 +0,0 @@ - - - - - Debug - AnyCPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46} - Exe - Properties - NadekoBot - NadekoBot - v4.5.2 - 512 - true - false - - - - C:\Users\Master\Desktop\NadekoBot\ - true - Disk - false - Foreground - 7 - Days - false - false - true - publish.htm - true - 1 - 0.5.0.%2a - false - true - true - - - AnyCPU - true - full - false - bin\Debug\ - TRACE;DEBUG - prompt - 4 - true - - - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - true - bin\PRIVATE\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\Release\ - TRACE;NADEKO_RELEASE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - true - bin\Debug\ - TRACE;DEBUG - full - x64 - prompt - MinimumRecommendedRules.ruleset - true - false - - - bin\x64\Release\ - TRACE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - true - bin\x64\PRIVATE\ - DEBUG;TRACE - full - x64 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\Release\ - TRACE;NADEKO_RELEASE - true - pdbonly - x64 - prompt - MinimumRecommendedRules.ruleset - true - true - - - - ..\packages\VideoLibrary.1.3.3\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\libvideo.dll - True - - - ..\packages\Manatee.Json.3.2.1\lib\net45\Manatee.Json.dll - True - - - ..\packages\Manatee.StateMachine.1.1.2\lib\net45\Manatee.StateMachine.dll - True - - - ..\packages\Manatee.Trello.1.8.2\lib\net45\Manatee.Trello.dll - True - - - ..\packages\Manatee.Trello.ManateeJson.1.4.0\lib\net45\Manatee.Trello.ManateeJson.dll - True - - - ..\packages\Manatee.Trello.WebApi.1.0.1\lib\net45\Manatee.Trello.WebApi.dll - True - - - ..\packages\MathosParser.1.0.10.1\lib\MathParser.dll - True - - - ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll - True - - - False - lib\ScaredFingers.UnitsConversion.dll - - - Classes\lib\sqlite3.dll - - - - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - True - - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - Designer - - - - - False - Microsoft .NET Framework 4.5.2 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - PublicResXFileCodeGenerator - Designer - Resources.Designer.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {7bfef748-b934-4621-9b11-6302e3a9f6b3} - Discord.Net.Audio - - - {1b5603b4-6f8f-4289-b945-7baae523d740} - Discord.Net.Commands - - - {3091164f-66ae-4543-a63d-167c1116241d} - Discord.Net.Modules - - - {8d71a857-879a-4a10-859e-5ff824ed6688} - Discord.Net - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - \ No newline at end of file diff --git a/NadekoBot/Properties/AssemblyInfo.cs b/NadekoBot/Properties/AssemblyInfo.cs deleted file mode 100644 index 2f38b185..00000000 --- a/NadekoBot/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyDescription("Discord bot written in C#.")] -[assembly: AssemblyCompany("Kwoth")] -[assembly: AssemblyCopyright("Copyright © Kwoth 2015-2016")] -[assembly: AssemblyProduct("NadekoBot")] -[assembly: AssemblyVersion("0.9.*")] -[assembly: AssemblyTitle("NadekoBot")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("27a886f5-cdda-4f4a-81ee-6dafcce9de46")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] diff --git a/NadekoBot/Properties/Resources.Designer.cs b/NadekoBot/Properties/Resources.Designer.cs deleted file mode 100644 index 7a7bb7a4..00000000 --- a/NadekoBot/Properties/Resources.Designer.cs +++ /dev/null @@ -1,743 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace NadekoBot.Properties { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - public class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NadekoBot.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - public static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _0 { - get { - object obj = ResourceManager.GetObject("_0", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _1 { - get { - object obj = ResourceManager.GetObject("_1", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _10_of_clubs { - get { - object obj = ResourceManager.GetObject("_10_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _10_of_diamonds { - get { - object obj = ResourceManager.GetObject("_10_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _10_of_hearts { - get { - object obj = ResourceManager.GetObject("_10_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _10_of_spades { - get { - object obj = ResourceManager.GetObject("_10_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _2 { - get { - object obj = ResourceManager.GetObject("_2", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _2_of_clubs { - get { - object obj = ResourceManager.GetObject("_2_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _2_of_diamonds { - get { - object obj = ResourceManager.GetObject("_2_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _2_of_hearts { - get { - object obj = ResourceManager.GetObject("_2_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _2_of_spades { - get { - object obj = ResourceManager.GetObject("_2_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _3 { - get { - object obj = ResourceManager.GetObject("_3", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _3_of_clubs { - get { - object obj = ResourceManager.GetObject("_3_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _3_of_diamonds { - get { - object obj = ResourceManager.GetObject("_3_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _3_of_hearts { - get { - object obj = ResourceManager.GetObject("_3_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _3_of_spades { - get { - object obj = ResourceManager.GetObject("_3_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _4 { - get { - object obj = ResourceManager.GetObject("_4", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _4_of_clubs { - get { - object obj = ResourceManager.GetObject("_4_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _4_of_diamonds { - get { - object obj = ResourceManager.GetObject("_4_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _4_of_hearts { - get { - object obj = ResourceManager.GetObject("_4_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _4_of_spades { - get { - object obj = ResourceManager.GetObject("_4_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _5 { - get { - object obj = ResourceManager.GetObject("_5", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _5_of_clubs { - get { - object obj = ResourceManager.GetObject("_5_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _5_of_diamonds { - get { - object obj = ResourceManager.GetObject("_5_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _5_of_hearts { - get { - object obj = ResourceManager.GetObject("_5_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _5_of_spades { - get { - object obj = ResourceManager.GetObject("_5_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _6 { - get { - object obj = ResourceManager.GetObject("_6", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _6_of_clubs { - get { - object obj = ResourceManager.GetObject("_6_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _6_of_diamonds { - get { - object obj = ResourceManager.GetObject("_6_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _6_of_hearts { - get { - object obj = ResourceManager.GetObject("_6_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _6_of_spades { - get { - object obj = ResourceManager.GetObject("_6_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _7 { - get { - object obj = ResourceManager.GetObject("_7", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _7_of_clubs { - get { - object obj = ResourceManager.GetObject("_7_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _7_of_diamonds { - get { - object obj = ResourceManager.GetObject("_7_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _7_of_hearts { - get { - object obj = ResourceManager.GetObject("_7_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _7_of_spades { - get { - object obj = ResourceManager.GetObject("_7_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _8 { - get { - object obj = ResourceManager.GetObject("_8", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _8_of_clubs { - get { - object obj = ResourceManager.GetObject("_8_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _8_of_diamonds { - get { - object obj = ResourceManager.GetObject("_8_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _8_of_hearts { - get { - object obj = ResourceManager.GetObject("_8_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _8_of_spades { - get { - object obj = ResourceManager.GetObject("_8_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _9 { - get { - object obj = ResourceManager.GetObject("_9", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _9_of_clubs { - get { - object obj = ResourceManager.GetObject("_9_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _9_of_diamonds { - get { - object obj = ResourceManager.GetObject("_9_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _9_of_hearts { - get { - object obj = ResourceManager.GetObject("_9_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap _9_of_spades { - get { - object obj = ResourceManager.GetObject("_9_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap ace_of_clubs { - get { - object obj = ResourceManager.GetObject("ace_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap ace_of_diamonds { - get { - object obj = ResourceManager.GetObject("ace_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap ace_of_hearts { - get { - object obj = ResourceManager.GetObject("ace_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap ace_of_spades { - get { - object obj = ResourceManager.GetObject("ace_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap black_joker { - get { - object obj = ResourceManager.GetObject("black_joker", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap heads { - get { - object obj = ResourceManager.GetObject("heads", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap jack_of_clubs { - get { - object obj = ResourceManager.GetObject("jack_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap jack_of_diamonds { - get { - object obj = ResourceManager.GetObject("jack_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap jack_of_hearts { - get { - object obj = ResourceManager.GetObject("jack_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap jack_of_spades { - get { - object obj = ResourceManager.GetObject("jack_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap king_of_clubs { - get { - object obj = ResourceManager.GetObject("king_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap king_of_diamonds { - get { - object obj = ResourceManager.GetObject("king_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap king_of_hearts { - get { - object obj = ResourceManager.GetObject("king_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap king_of_spades { - get { - object obj = ResourceManager.GetObject("king_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap queen_of_clubs { - get { - object obj = ResourceManager.GetObject("queen_of_clubs", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap queen_of_diamonds { - get { - object obj = ResourceManager.GetObject("queen_of_diamonds", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap queen_of_hearts { - get { - object obj = ResourceManager.GetObject("queen_of_hearts", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap queen_of_spades { - get { - object obj = ResourceManager.GetObject("queen_of_spades", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap red_joker { - get { - object obj = ResourceManager.GetObject("red_joker", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap rip { - get { - object obj = ResourceManager.GetObject("rip", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap rose_overlay { - get { - object obj = ResourceManager.GetObject("rose_overlay", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - public static System.Drawing.Bitmap tails { - get { - object obj = ResourceManager.GetObject("tails", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - } -} diff --git a/NadekoBot/Properties/Resources.resx b/NadekoBot/Properties/Resources.resx deleted file mode 100644 index b1b62029..00000000 --- a/NadekoBot/Properties/Resources.resx +++ /dev/null @@ -1,325 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - ..\resources\images\cards\ace_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\ace_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\ace_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\ace_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\black_joker.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\coins\heads.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\jack_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\jack_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\jack_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\jack_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\king_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\king_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\king_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\king_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\queen_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\queen_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\queen_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\queen_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\red_joker.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\rip.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\coins\tails.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\0.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\1.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\10_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\10_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\10_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\10_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\2.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\2_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\2_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\2_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\2_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\3.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\3_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\3_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\3_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\3_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\4.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\4_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\4_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\4_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\4_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\5.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\5_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\5_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\5_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\5_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\6.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\6_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\6_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\6_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\6_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\7.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\7_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\7_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\7_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\7_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\8.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\8_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\8_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\8_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\8_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\dice\9.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\9_of_clubs.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\9_of_diamonds.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\9_of_hearts.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\cards\9_of_spades.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - - ..\resources\images\rose_overlay.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - \ No newline at end of file diff --git a/NadekoBot/SQLite.cs b/NadekoBot/SQLite.cs deleted file mode 100644 index e74447f6..00000000 --- a/NadekoBot/SQLite.cs +++ /dev/null @@ -1,3278 +0,0 @@ -// -// Copyright (c) 2009-2012 Krueger Systems, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -#if WINDOWS_PHONE && !USE_WP8_NATIVE_SQLITE -#define USE_CSHARP_SQLITE -#endif - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Collections.Generic; -using System.Reflection; -using System.Linq; -using System.Linq.Expressions; -using System.Threading; - -#if USE_CSHARP_SQLITE -using Sqlite3 = Community.CsharpSqlite.Sqlite3; -using Sqlite3DatabaseHandle = Community.CsharpSqlite.Sqlite3.sqlite3; -using Sqlite3Statement = Community.CsharpSqlite.Sqlite3.Vdbe; -#elif USE_WP8_NATIVE_SQLITE -using Sqlite3 = Sqlite.Sqlite3; -using Sqlite3DatabaseHandle = Sqlite.Database; -using Sqlite3Statement = Sqlite.Statement; -#else -using Sqlite3DatabaseHandle = System.IntPtr; -using Sqlite3Statement = System.IntPtr; -#endif - -namespace SQLite -{ - public class SQLiteException : Exception - { - public SQLite3.Result Result { get; private set; } - - protected SQLiteException (SQLite3.Result r,string message) : base(message) - { - Result = r; - } - - public static SQLiteException New (SQLite3.Result r, string message) - { - return new SQLiteException (r, message); - } - } - - public class NotNullConstraintViolationException : SQLiteException - { - public IEnumerable Columns { get; protected set; } - - protected NotNullConstraintViolationException (SQLite3.Result r, string message) - : this (r, message, null, null) - { - - } - - protected NotNullConstraintViolationException (SQLite3.Result r, string message, TableMapping mapping, object obj) - : base (r, message) - { - if (mapping != null && obj != null) { - this.Columns = from c in mapping.Columns - where c.IsNullable == false && c.GetValue (obj) == null - select c; - } - } - - public static new NotNullConstraintViolationException New (SQLite3.Result r, string message) - { - return new NotNullConstraintViolationException (r, message); - } - - public static NotNullConstraintViolationException New (SQLite3.Result r, string message, TableMapping mapping, object obj) - { - return new NotNullConstraintViolationException (r, message, mapping, obj); - } - - public static NotNullConstraintViolationException New (SQLiteException exception, TableMapping mapping, object obj) - { - return new NotNullConstraintViolationException (exception.Result, exception.Message, mapping, obj); - } - } - - [Flags] - public enum SQLiteOpenFlags { - ReadOnly = 1, ReadWrite = 2, Create = 4, - NoMutex = 0x8000, FullMutex = 0x10000, - SharedCache = 0x20000, PrivateCache = 0x40000, - ProtectionComplete = 0x00100000, - ProtectionCompleteUnlessOpen = 0x00200000, - ProtectionCompleteUntilFirstUserAuthentication = 0x00300000, - ProtectionNone = 0x00400000 - } - - [Flags] - public enum CreateFlags - { - None = 0, - ImplicitPK = 1, // create a primary key for field called 'Id' (Orm.ImplicitPkName) - ImplicitIndex = 2, // create an index for fields ending in 'Id' (Orm.ImplicitIndexSuffix) - AllImplicit = 3, // do both above - - AutoIncPK = 4 // force PK field to be auto inc - } - - /// - /// Represents an open connection to a SQLite database. - /// - public partial class SQLiteConnection : IDisposable - { - private bool _open; - private TimeSpan _busyTimeout; - private Dictionary _mappings = null; - private Dictionary _tables = null; - private System.Diagnostics.Stopwatch _sw; - private long _elapsedMilliseconds = 0; - - private int _transactionDepth = 0; - private Random _rand = new Random (); - - public Sqlite3DatabaseHandle Handle { get; private set; } - internal static readonly Sqlite3DatabaseHandle NullHandle = default(Sqlite3DatabaseHandle); - - public string DatabasePath { get; private set; } - - public bool TimeExecution { get; set; } - - public bool Trace { get; set; } - - public bool StoreDateTimeAsTicks { get; private set; } - - /// - /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. - /// - /// - /// Specifies the path to the database file. - /// - /// - /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You - /// absolutely do want to store them as Ticks in all new projects. The default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection (string databasePath, bool storeDateTimeAsTicks = false) - : this (databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) - { - } - - /// - /// Constructs a new SQLiteConnection and opens a SQLite database specified by databasePath. - /// - /// - /// Specifies the path to the database file. - /// - /// - /// Specifies whether to store DateTime properties as ticks (true) or strings (false). You - /// absolutely do want to store them as Ticks in all new projects. The default of false is - /// only here for backwards compatibility. There is a *significant* speed advantage, with no - /// down sides, when setting storeDateTimeAsTicks = true. - /// - public SQLiteConnection (string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) - { - if (string.IsNullOrEmpty (databasePath)) - throw new ArgumentException ("Must be specified", "databasePath"); - - DatabasePath = databasePath; - -#if NETFX_CORE - SQLite3.SetDirectory(/*temp directory type*/2, Windows.Storage.ApplicationData.Current.TemporaryFolder.Path); -#endif - - Sqlite3DatabaseHandle handle; - -#if SILVERLIGHT || USE_CSHARP_SQLITE - var r = SQLite3.Open (databasePath, out handle, (int)openFlags, IntPtr.Zero); -#else - // open using the byte[] - // in the case where the path may include Unicode - // force open to using UTF-8 using sqlite3_open_v2 - var databasePathAsBytes = GetNullTerminatedUtf8 (DatabasePath); - var r = SQLite3.Open (databasePathAsBytes, out handle, (int) openFlags, IntPtr.Zero); -#endif - - Handle = handle; - if (r != SQLite3.Result.OK) { - throw SQLiteException.New (r, String.Format ("Could not open database file: {0} ({1})", DatabasePath, r)); - } - _open = true; - - StoreDateTimeAsTicks = storeDateTimeAsTicks; - - BusyTimeout = TimeSpan.FromSeconds (0.1); - } - - static SQLiteConnection () - { - if (_preserveDuringLinkMagic) { - var ti = new ColumnInfo (); - ti.Name = "magic"; - } - } - - public void EnableLoadExtension(int onoff) - { - SQLite3.Result r = SQLite3.EnableLoadExtension(Handle, onoff); - if (r != SQLite3.Result.OK) { - string msg = SQLite3.GetErrmsg (Handle); - throw SQLiteException.New (r, msg); - } - } - - static byte[] GetNullTerminatedUtf8 (string s) - { - var utf8Length = System.Text.Encoding.UTF8.GetByteCount (s); - var bytes = new byte [utf8Length + 1]; - utf8Length = System.Text.Encoding.UTF8.GetBytes(s, 0, s.Length, bytes, 0); - return bytes; - } - - /// - /// Used to list some code that we want the MonoTouch linker - /// to see, but that we never want to actually execute. - /// - static bool _preserveDuringLinkMagic; - - /// - /// Sets a busy handler to sleep the specified amount of time when a table is locked. - /// The handler will sleep multiple times until a total time of has accumulated. - /// - public TimeSpan BusyTimeout { - get { return _busyTimeout; } - set { - _busyTimeout = value; - if (Handle != NullHandle) { - SQLite3.BusyTimeout (Handle, (int)_busyTimeout.TotalMilliseconds); - } - } - } - - /// - /// Returns the mappings from types to tables that the connection - /// currently understands. - /// - public IEnumerable TableMappings { - get { - return _tables != null ? _tables.Values : Enumerable.Empty (); - } - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The type whose mapping to the database is returned. - /// - /// - /// Optional flags allowing implicit PK and indexes based on naming conventions - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping(Type type, CreateFlags createFlags = CreateFlags.None) - { - if (_mappings == null) { - _mappings = new Dictionary (); - } - TableMapping map; - if (!_mappings.TryGetValue (type.FullName, out map)) { - map = new TableMapping (type, createFlags); - _mappings [type.FullName] = map; - } - return map; - } - - /// - /// Retrieves the mapping that is automatically generated for the given type. - /// - /// - /// The mapping represents the schema of the columns of the database and contains - /// methods to set and get properties of objects. - /// - public TableMapping GetMapping () - { - return GetMapping (typeof (T)); - } - - private struct IndexedColumn - { - public int Order; - public string ColumnName; - } - - private struct IndexInfo - { - public string IndexName; - public string TableName; - public bool Unique; - public List Columns; - } - - /// - /// Executes a "drop table" on the database. This is non-recoverable. - /// - public int DropTable() - { - var map = GetMapping (typeof (T)); - - var query = string.Format("drop table if exists \"{0}\"", map.TableName); - - return Execute (query); - } - - /// - /// Executes a "create table if not exists" on the database. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(CreateFlags createFlags = CreateFlags.None) - { - return CreateTable(typeof (T), createFlags); - } - - /// - /// Executes a "create table if not exists" on the database. It also - /// creates any specified indexes on the columns of the table. It uses - /// a schema automatically generated from the specified type. You can - /// later access this schema by calling GetMapping. - /// - /// Type to reflect to a database table. - /// Optional flags allowing implicit PK and indexes based on naming conventions. - /// - /// The number of entries added to the database schema. - /// - public int CreateTable(Type ty, CreateFlags createFlags = CreateFlags.None) - { - if (_tables == null) { - _tables = new Dictionary (); - } - TableMapping map; - if (!_tables.TryGetValue (ty.FullName, out map)) { - map = GetMapping (ty, createFlags); - _tables.Add (ty.FullName, map); - } - var query = "create table if not exists \"" + map.TableName + "\"(\n"; - - var decls = map.Columns.Select (p => Orm.SqlDecl (p, StoreDateTimeAsTicks)); - var decl = string.Join (",\n", decls.ToArray ()); - query += decl; - query += ")"; - - var count = Execute (query); - - if (count == 0) { //Possible bug: This always seems to return 0? - // Table already exists, migrate it - MigrateTable (map); - } - - var indexes = new Dictionary (); - foreach (var c in map.Columns) { - foreach (var i in c.Indices) { - var iname = i.Name ?? map.TableName + "_" + c.Name; - IndexInfo iinfo; - if (!indexes.TryGetValue (iname, out iinfo)) { - iinfo = new IndexInfo { - IndexName = iname, - TableName = map.TableName, - Unique = i.Unique, - Columns = new List () - }; - indexes.Add (iname, iinfo); - } - - if (i.Unique != iinfo.Unique) - throw new Exception ("All the columns in an index must have the same value for their Unique property"); - - iinfo.Columns.Add (new IndexedColumn { - Order = i.Order, - ColumnName = c.Name - }); - } - } - - foreach (var indexName in indexes.Keys) { - var index = indexes[indexName]; - var columns = index.Columns.OrderBy(i => i.Order).Select(i => i.ColumnName).ToArray(); - count += CreateIndex(indexName, index.TableName, columns, index.Unique); - } - - return count; - } - - /// - /// Creates an index for the specified table and columns. - /// - /// Name of the index to create - /// Name of the database table - /// An array of column names to index - /// Whether the index should be unique - public int CreateIndex(string indexName, string tableName, string[] columnNames, bool unique = false) - { - const string sqlFormat = "create {2} index if not exists \"{3}\" on \"{0}\"(\"{1}\")"; - var sql = String.Format(sqlFormat, tableName, string.Join ("\", \"", columnNames), unique ? "unique" : "", indexName); - return Execute(sql); - } - - /// - /// Creates an index for the specified table and column. - /// - /// Name of the index to create - /// Name of the database table - /// Name of the column to index - /// Whether the index should be unique - public int CreateIndex(string indexName, string tableName, string columnName, bool unique = false) - { - return CreateIndex(indexName, tableName, new string[] { columnName }, unique); - } - - /// - /// Creates an index for the specified table and column. - /// - /// Name of the database table - /// Name of the column to index - /// Whether the index should be unique - public int CreateIndex(string tableName, string columnName, bool unique = false) - { - return CreateIndex(tableName + "_" + columnName, tableName, columnName, unique); - } - - /// - /// Creates an index for the specified table and columns. - /// - /// Name of the database table - /// An array of column names to index - /// Whether the index should be unique - public int CreateIndex(string tableName, string[] columnNames, bool unique = false) - { - return CreateIndex(tableName + "_" + string.Join ("_", columnNames), tableName, columnNames, unique); - } - - /// - /// Creates an index for the specified object property. - /// e.g. CreateIndex(c => c.Name); - /// - /// Type to reflect to a database table. - /// Property to index - /// Whether the index should be unique - public void CreateIndex(Expression> property, bool unique = false) - { - MemberExpression mx; - if (property.Body.NodeType == ExpressionType.Convert) - { - mx = ((UnaryExpression)property.Body).Operand as MemberExpression; - } - else - { - mx= (property.Body as MemberExpression); - } - var propertyInfo = mx.Member as PropertyInfo; - if (propertyInfo == null) - { - throw new ArgumentException("The lambda expression 'property' should point to a valid Property"); - } - - var propName = propertyInfo.Name; - - var map = GetMapping(); - var colName = map.FindColumnWithPropertyName(propName).Name; - - CreateIndex(map.TableName, colName, unique); - } - - public class ColumnInfo - { -// public int cid { get; set; } - - [Column ("name")] - public string Name { get; set; } - -// [Column ("type")] -// public string ColumnType { get; set; } - - public int notnull { get; set; } - -// public string dflt_value { get; set; } - -// public int pk { get; set; } - - public override string ToString () - { - return Name; - } - } - - public List GetTableInfo (string tableName) - { - var query = "pragma table_info(\"" + tableName + "\")"; - return Query (query); - } - - void MigrateTable (TableMapping map) - { - var existingCols = GetTableInfo (map.TableName); - - var toBeAdded = new List (); - - foreach (var p in map.Columns) { - var found = false; - foreach (var c in existingCols) { - found = (string.Compare (p.Name, c.Name, StringComparison.OrdinalIgnoreCase) == 0); - if (found) - break; - } - if (!found) { - toBeAdded.Add (p); - } - } - - foreach (var p in toBeAdded) { - var addCol = "alter table \"" + map.TableName + "\" add column " + Orm.SqlDecl (p, StoreDateTimeAsTicks); - Execute (addCol); - } - } - - /// - /// Creates a new SQLiteCommand. Can be overridden to provide a sub-class. - /// - /// - protected virtual SQLiteCommand NewCommand () - { - return new SQLiteCommand (this); - } - - /// - /// Creates a new SQLiteCommand given the command text with arguments. Place a '?' - /// in the command text for each of the arguments. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the command text. - /// - /// - /// A - /// - public SQLiteCommand CreateCommand (string cmdText, params object[] ps) - { - if (!_open) - throw SQLiteException.New (SQLite3.Result.Error, "Cannot create commands from unopened database"); - - var cmd = NewCommand (); - cmd.CommandText = cmdText; - foreach (var o in ps) { - cmd.Bind (o); - } - return cmd; - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// Use this method instead of Query when you don't expect rows back. Such cases include - /// INSERTs, UPDATEs, and DELETEs. - /// You can set the Trace or TimeExecution properties of the connection - /// to profile execution. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// The number of rows modified in the database as a result of this execution. - /// - public int Execute (string query, params object[] args) - { - var cmd = CreateCommand (query, args); - - if (TimeExecution) { - if (_sw == null) { - _sw = new Stopwatch (); - } - _sw.Reset (); - _sw.Start (); - } - - var r = cmd.ExecuteNonQuery (); - - if (TimeExecution) { - _sw.Stop (); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - public T ExecuteScalar (string query, params object[] args) - { - var cmd = CreateCommand (query, args); - - if (TimeExecution) { - if (_sw == null) { - _sw = new Stopwatch (); - } - _sw.Reset (); - _sw.Start (); - } - - var r = cmd.ExecuteScalar (); - - if (TimeExecution) { - _sw.Stop (); - _elapsedMilliseconds += _sw.ElapsedMilliseconds; - Debug.WriteLine (string.Format ("Finished in {0} ms ({1:0.0} s total)", _sw.ElapsedMilliseconds, _elapsedMilliseconds / 1000.0)); - } - - return r; - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the mapping automatically generated for - /// the given type. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// - public List Query (string query, params object[] args) where T : new() - { - var cmd = CreateCommand (query, args); - return cmd.ExecuteQuery (); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the mapping automatically generated for - /// the given type. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// The enumerator will call sqlite3_step on each call to MoveNext, so the database - /// connection must remain open for the lifetime of the enumerator. - /// - public IEnumerable DeferredQuery(string query, params object[] args) where T : new() - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A to use to convert the resulting rows - /// into objects. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// - public List Query (TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand (query, args); - return cmd.ExecuteQuery (map); - } - - /// - /// Creates a SQLiteCommand given the command text (SQL) with arguments. Place a '?' - /// in the command text for each of the arguments and then executes that command. - /// It returns each row of the result using the specified mapping. This function is - /// only used by libraries in order to query the database via introspection. It is - /// normally not used. - /// - /// - /// A to use to convert the resulting rows - /// into objects. - /// - /// - /// The fully escaped SQL. - /// - /// - /// Arguments to substitute for the occurences of '?' in the query. - /// - /// - /// An enumerable with one result for each row returned by the query. - /// The enumerator will call sqlite3_step on each call to MoveNext, so the database - /// connection must remain open for the lifetime of the enumerator. - /// - public IEnumerable DeferredQuery(TableMapping map, string query, params object[] args) - { - var cmd = CreateCommand(query, args); - return cmd.ExecuteDeferredQuery(map); - } - - /// - /// Returns a queryable interface to the table represented by the given type. - /// - /// - /// A queryable object that is able to translate Where, OrderBy, and Take - /// queries into native SQL. - /// - public TableQuery Table () where T : new() - { - return new TableQuery (this); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The object with the given primary key. Throws a not found exception - /// if the object is not found. - /// - public T Get (object pk) where T : new() - { - var map = GetMapping (typeof(T)); - return Query (map.GetByPrimaryKeySql, pk).First (); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate. Throws a not found exception - /// if the object is not found. - /// - public T Get (Expression> predicate) where T : new() - { - return Table ().Where (predicate).First (); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public T Find (object pk) where T : new () - { - var map = GetMapping (typeof (T)); - return Query (map.GetByPrimaryKeySql, pk).FirstOrDefault (); - } - - /// - /// Attempts to retrieve an object with the given primary key from the table - /// associated with the specified type. Use of this method requires that - /// the given type have a designated PrimaryKey (using the PrimaryKeyAttribute). - /// - /// - /// The primary key. - /// - /// - /// The TableMapping used to identify the object type. - /// - /// - /// The object with the given primary key or null - /// if the object is not found. - /// - public object Find (object pk, TableMapping map) - { - return Query (map, map.GetByPrimaryKeySql, pk).FirstOrDefault (); - } - - /// - /// Attempts to retrieve the first object that matches the predicate from the table - /// associated with the specified type. - /// - /// - /// A predicate for which object to find. - /// - /// - /// The object that matches the given predicate or null - /// if the object is not found. - /// - public T Find (Expression> predicate) where T : new() - { - return Table ().Where (predicate).FirstOrDefault (); - } - - /// - /// Whether has been called and the database is waiting for a . - /// - public bool IsInTransaction { - get { return _transactionDepth > 0; } - } - - /// - /// Begins a new transaction. Call to end the transaction. - /// - /// Throws if a transaction has already begun. - public void BeginTransaction () - { - // The BEGIN command only works if the transaction stack is empty, - // or in other words if there are no pending transactions. - // If the transaction stack is not empty when the BEGIN command is invoked, - // then the command fails with an error. - // Rather than crash with an error, we will just ignore calls to BeginTransaction - // that would result in an error. - if (Interlocked.CompareExchange (ref _transactionDepth, 1, 0) == 0) { - try { - Execute ("begin transaction"); - } catch (Exception ex) { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo (null, true); - break; - } - } else { - // Call decrement and not VolatileWrite in case we've already - // created a transaction point in SaveTransactionPoint since the catch. - Interlocked.Decrement (ref _transactionDepth); - } - - throw; - } - } else { - // Calling BeginTransaction on an already open transaction is invalid - throw new InvalidOperationException ("Cannot begin a transaction while already in a transaction."); - } - } - - /// - /// Creates a savepoint in the database at the current point in the transaction timeline. - /// Begins a new transaction if one is not in progress. - /// - /// Call to undo transactions since the returned savepoint. - /// Call to commit transactions after the savepoint returned here. - /// Call to end the transaction, committing all changes. - /// - /// A string naming the savepoint. - public string SaveTransactionPoint () - { - int depth = Interlocked.Increment (ref _transactionDepth) - 1; - string retVal = "S" + _rand.Next (short.MaxValue) + "D" + depth; - - try { - Execute ("savepoint " + retVal); - } catch (Exception ex) { - var sqlExp = ex as SQLiteException; - if (sqlExp != null) { - // It is recommended that applications respond to the errors listed below - // by explicitly issuing a ROLLBACK command. - // TODO: This rollback failsafe should be localized to all throw sites. - switch (sqlExp.Result) { - case SQLite3.Result.IOError: - case SQLite3.Result.Full: - case SQLite3.Result.Busy: - case SQLite3.Result.NoMem: - case SQLite3.Result.Interrupt: - RollbackTo (null, true); - break; - } - } else { - Interlocked.Decrement (ref _transactionDepth); - } - - throw; - } - - return retVal; - } - - /// - /// Rolls back the transaction that was begun by or . - /// - public void Rollback () - { - RollbackTo (null, false); - } - - /// - /// Rolls back the savepoint created by or SaveTransactionPoint. - /// - /// The name of the savepoint to roll back to, as returned by . If savepoint is null or empty, this method is equivalent to a call to - public void RollbackTo (string savepoint) - { - RollbackTo (savepoint, false); - } - - /// - /// Rolls back the transaction that was begun by . - /// - /// true to avoid throwing exceptions, false otherwise - void RollbackTo (string savepoint, bool noThrow) - { - // Rolling back without a TO clause rolls backs all transactions - // and leaves the transaction stack empty. - try { - if (String.IsNullOrEmpty (savepoint)) { - if (Interlocked.Exchange (ref _transactionDepth, 0) > 0) { - Execute ("rollback"); - } - } else { - DoSavePointExecute (savepoint, "rollback to "); - } - } catch (SQLiteException) { - if (!noThrow) - throw; - - } - // No need to rollback if there are no transactions open. - } - - /// - /// Releases a savepoint returned from . Releasing a savepoint - /// makes changes since that savepoint permanent if the savepoint began the transaction, - /// or otherwise the changes are permanent pending a call to . - /// - /// The RELEASE command is like a COMMIT for a SAVEPOINT. - /// - /// The name of the savepoint to release. The string should be the result of a call to - public void Release (string savepoint) - { - DoSavePointExecute (savepoint, "release "); - } - - void DoSavePointExecute (string savepoint, string cmd) - { - // Validate the savepoint - int firstLen = savepoint.IndexOf ('D'); - if (firstLen >= 2 && savepoint.Length > firstLen + 1) { - int depth; - if (Int32.TryParse (savepoint.Substring (firstLen + 1), out depth)) { - // TODO: Mild race here, but inescapable without locking almost everywhere. - if (0 <= depth && depth < _transactionDepth) { -#if NETFX_CORE - Volatile.Write (ref _transactionDepth, depth); -#elif SILVERLIGHT - _transactionDepth = depth; -#else - Thread.VolatileWrite (ref _transactionDepth, depth); -#endif - Execute (cmd + savepoint); - return; - } - } - } - - throw new ArgumentException ("savePoint is not valid, and should be the result of a call to SaveTransactionPoint.", "savePoint"); - } - - /// - /// Commits the transaction that was begun by . - /// - public void Commit () - { - if (Interlocked.Exchange (ref _transactionDepth, 0) != 0) { - Execute ("commit"); - } - // Do nothing on a commit with no open transaction - } - - /// - /// Executes within a (possibly nested) transaction by wrapping it in a SAVEPOINT. If an - /// exception occurs the whole transaction is rolled back, not just the current savepoint. The exception - /// is rethrown. - /// - /// - /// The to perform within a transaction. can contain any number - /// of operations on the connection but should never call or - /// . - /// - public void RunInTransaction (Action action) - { - try { - var savePoint = SaveTransactionPoint (); - action (); - Release (savePoint); - } catch (Exception) { - Rollback (); - throw; - } - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction(() => { - foreach (var r in objects) { - c += Insert (r); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects, string extra) - { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Insert (r, extra); - } - }); - return c; - } - - /// - /// Inserts all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int InsertAll (System.Collections.IEnumerable objects, Type objType) - { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Insert (r, objType); - } - }); - return c; - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj) - { - if (obj == null) { - return 0; - } - return Insert (obj, "", obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// If a UNIQUE constraint violation occurs with - /// some pre-existing object, this function deletes - /// the old object. - /// - /// - /// The object to insert. - /// - /// - /// The number of rows modified. - /// - public int InsertOrReplace (object obj) - { - if (obj == null) { - return 0; - } - return Insert (obj, "OR REPLACE", obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, Type objType) - { - return Insert (obj, "", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// If a UNIQUE constraint violation occurs with - /// some pre-existing object, this function deletes - /// the old object. - /// - /// - /// The object to insert. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows modified. - /// - public int InsertOrReplace (object obj, Type objType) - { - return Insert (obj, "OR REPLACE", objType); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, string extra) - { - if (obj == null) { - return 0; - } - return Insert (obj, extra, obj.GetType ()); - } - - /// - /// Inserts the given object and retrieves its - /// auto incremented primary key if it has one. - /// - /// - /// The object to insert. - /// - /// - /// Literal SQL code that gets placed into the command. INSERT {extra} INTO ... - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows added to the table. - /// - public int Insert (object obj, string extra, Type objType) - { - if (obj == null || objType == null) { - return 0; - } - - - var map = GetMapping (objType); - -#if NETFX_CORE - if (map.PK != null && map.PK.IsAutoGuid) - { - // no GetProperty so search our way up the inheritance chain till we find it - PropertyInfo prop; - while (objType != null) - { - var info = objType.GetTypeInfo(); - prop = info.GetDeclaredProperty(map.PK.PropertyName); - if (prop != null) - { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) - { - prop.SetValue(obj, Guid.NewGuid(), null); - } - break; - } - - objType = info.BaseType; - } - } -#else - if (map.PK != null && map.PK.IsAutoGuid) { - var prop = objType.GetProperty(map.PK.PropertyName); - if (prop != null) { - if (prop.GetValue(obj, null).Equals(Guid.Empty)) { - prop.SetValue(obj, Guid.NewGuid(), null); - } - } - } -#endif - - - var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns; - var vals = new object[cols.Length]; - for (var i = 0; i < vals.Length; i++) { - vals [i] = cols [i].GetValue (obj); - } - - var insertCmd = map.GetInsertCommand (this, extra); - int count; - - try { - count = insertCmd.ExecuteNonQuery (vals); - } - catch (SQLiteException ex) { - - if (SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (ex.Result, ex.Message, map, obj); - } - throw; - } - - if (map.HasAutoIncPK) - { - var id = SQLite3.LastInsertRowid (Handle); - map.SetAutoIncPK (obj, id); - } - - return count; - } - - /// - /// Updates all of the columns of a table using the specified object - /// except for its primary key. - /// The object is required to have a primary key. - /// - /// - /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The number of rows updated. - /// - public int Update (object obj) - { - if (obj == null) { - return 0; - } - return Update (obj, obj.GetType ()); - } - - /// - /// Updates all of the columns of a table using the specified object - /// except for its primary key. - /// The object is required to have a primary key. - /// - /// - /// The object to update. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The type of object to insert. - /// - /// - /// The number of rows updated. - /// - public int Update (object obj, Type objType) - { - int rowsAffected = 0; - if (obj == null || objType == null) { - return 0; - } - - var map = GetMapping (objType); - - var pk = map.PK; - - if (pk == null) { - throw new NotSupportedException ("Cannot update " + map.TableName + ": it has no PK"); - } - - var cols = from p in map.Columns - where p != pk - select p; - var vals = from c in cols - select c.GetValue (obj); - var ps = new List (vals); - ps.Add (pk.GetValue (obj)); - var q = string.Format ("update \"{0}\" set {1} where {2} = ? ", map.TableName, string.Join (",", (from c in cols - select "\"" + c.Name + "\" = ? ").ToArray ()), pk.Name); - - try { - rowsAffected = Execute (q, ps.ToArray ()); - } - catch (SQLiteException ex) { - - if (ex.Result == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (this.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (ex, map, obj); - } - - throw ex; - } - - return rowsAffected; - } - - /// - /// Updates all specified objects. - /// - /// - /// An of the objects to insert. - /// - /// - /// The number of rows modified. - /// - public int UpdateAll (System.Collections.IEnumerable objects) - { - var c = 0; - RunInTransaction (() => { - foreach (var r in objects) { - c += Update (r); - } - }); - return c; - } - - /// - /// Deletes the given object from the database using its primary key. - /// - /// - /// The object to delete. It must have a primary key designated using the PrimaryKeyAttribute. - /// - /// - /// The number of rows deleted. - /// - public int Delete (object objectToDelete) - { - var map = GetMapping (objectToDelete.GetType ()); - var pk = map.PK; - if (pk == null) { - throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute (q, pk.GetValue (objectToDelete)); - } - - /// - /// Deletes the object with the specified primary key. - /// - /// - /// The primary key of the object to delete. - /// - /// - /// The number of objects deleted. - /// - /// - /// The type of object. - /// - public int Delete (object primaryKey) - { - var map = GetMapping (typeof (T)); - var pk = map.PK; - if (pk == null) { - throw new NotSupportedException ("Cannot delete " + map.TableName + ": it has no PK"); - } - var q = string.Format ("delete from \"{0}\" where \"{1}\" = ?", map.TableName, pk.Name); - return Execute (q, primaryKey); - } - - /// - /// Deletes all the objects from the specified table. - /// WARNING WARNING: Let me repeat. It deletes ALL the objects from the - /// specified table. Do you really want to do that? - /// - /// - /// The number of objects deleted. - /// - /// - /// The type of objects to delete. - /// - public int DeleteAll () - { - var map = GetMapping (typeof (T)); - var query = string.Format("delete from \"{0}\"", map.TableName); - return Execute (query); - } - - ~SQLiteConnection () - { - Dispose (false); - } - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - protected virtual void Dispose (bool disposing) - { - Close (); - } - - public void Close () - { - if (_open && Handle != NullHandle) { - try { - if (_mappings != null) { - foreach (var sqlInsertCommand in _mappings.Values) { - sqlInsertCommand.Dispose(); - } - } - var r = SQLite3.Close (Handle); - if (r != SQLite3.Result.OK) { - string msg = SQLite3.GetErrmsg (Handle); - throw SQLiteException.New (r, msg); - } - } - finally { - Handle = NullHandle; - _open = false; - } - } - } - } - - /// - /// Represents a parsed connection string. - /// - class SQLiteConnectionString - { - public string ConnectionString { get; private set; } - public string DatabasePath { get; private set; } - public bool StoreDateTimeAsTicks { get; private set; } - -#if NETFX_CORE - static readonly string MetroStyleDataPath = Windows.Storage.ApplicationData.Current.LocalFolder.Path; -#endif - - public SQLiteConnectionString (string databasePath, bool storeDateTimeAsTicks) - { - ConnectionString = databasePath; - StoreDateTimeAsTicks = storeDateTimeAsTicks; - -#if NETFX_CORE - DatabasePath = System.IO.Path.Combine (MetroStyleDataPath, databasePath); -#else - DatabasePath = databasePath; -#endif - } - } - - [AttributeUsage (AttributeTargets.Class)] - public class TableAttribute : Attribute - { - public string Name { get; set; } - - public TableAttribute (string name) - { - Name = name; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class ColumnAttribute : Attribute - { - public string Name { get; set; } - - public ColumnAttribute (string name) - { - Name = name; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class PrimaryKeyAttribute : Attribute - { - } - - [AttributeUsage (AttributeTargets.Property)] - public class AutoIncrementAttribute : Attribute - { - } - - [AttributeUsage (AttributeTargets.Property)] - public class IndexedAttribute : Attribute - { - public string Name { get; set; } - public int Order { get; set; } - public virtual bool Unique { get; set; } - - public IndexedAttribute() - { - } - - public IndexedAttribute(string name, int order) - { - Name = name; - Order = order; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class IgnoreAttribute : Attribute - { - } - - [AttributeUsage (AttributeTargets.Property)] - public class UniqueAttribute : IndexedAttribute - { - public override bool Unique { - get { return true; } - set { /* throw? */ } - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class MaxLengthAttribute : Attribute - { - public int Value { get; private set; } - - public MaxLengthAttribute (int length) - { - Value = length; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class CollationAttribute: Attribute - { - public string Value { get; private set; } - - public CollationAttribute (string collation) - { - Value = collation; - } - } - - [AttributeUsage (AttributeTargets.Property)] - public class NotNullAttribute : Attribute - { - } - - public class TableMapping - { - public Type MappedType { get; private set; } - - public string TableName { get; private set; } - - public Column[] Columns { get; private set; } - - public Column PK { get; private set; } - - public string GetByPrimaryKeySql { get; private set; } - - Column _autoPk; - Column[] _insertColumns; - Column[] _insertOrReplaceColumns; - - public TableMapping(Type type, CreateFlags createFlags = CreateFlags.None) - { - MappedType = type; - -#if NETFX_CORE - var tableAttr = (TableAttribute)System.Reflection.CustomAttributeExtensions - .GetCustomAttribute(type.GetTypeInfo(), typeof(TableAttribute), true); -#else - var tableAttr = (TableAttribute)type.GetCustomAttributes (typeof (TableAttribute), true).FirstOrDefault (); -#endif - - TableName = tableAttr != null ? tableAttr.Name : MappedType.Name; - -#if !NETFX_CORE - var props = MappedType.GetProperties (BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); -#else - var props = from p in MappedType.GetRuntimeProperties() - where ((p.GetMethod != null && p.GetMethod.IsPublic) || (p.SetMethod != null && p.SetMethod.IsPublic) || (p.GetMethod != null && p.GetMethod.IsStatic) || (p.SetMethod != null && p.SetMethod.IsStatic)) - select p; -#endif - var cols = new List (); - foreach (var p in props) { -#if !NETFX_CORE - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Length > 0; -#else - var ignore = p.GetCustomAttributes (typeof(IgnoreAttribute), true).Count() > 0; -#endif - if (p.CanWrite && !ignore) { - cols.Add (new Column (p, createFlags)); - } - } - Columns = cols.ToArray (); - foreach (var c in Columns) { - if (c.IsAutoInc && c.IsPK) { - _autoPk = c; - } - if (c.IsPK) { - PK = c; - } - } - - HasAutoIncPK = _autoPk != null; - - if (PK != null) { - GetByPrimaryKeySql = string.Format ("select * from \"{0}\" where \"{1}\" = ?", TableName, PK.Name); - } - else { - // People should not be calling Get/Find without a PK - GetByPrimaryKeySql = string.Format ("select * from \"{0}\" limit 1", TableName); - } - } - - public bool HasAutoIncPK { get; private set; } - - public void SetAutoIncPK (object obj, long id) - { - if (_autoPk != null) { - _autoPk.SetValue (obj, Convert.ChangeType (id, _autoPk.ColumnType, null)); - } - } - - public Column[] InsertColumns { - get { - if (_insertColumns == null) { - _insertColumns = Columns.Where (c => !c.IsAutoInc).ToArray (); - } - return _insertColumns; - } - } - - public Column[] InsertOrReplaceColumns { - get { - if (_insertOrReplaceColumns == null) { - _insertOrReplaceColumns = Columns.ToArray (); - } - return _insertOrReplaceColumns; - } - } - - public Column FindColumnWithPropertyName (string propertyName) - { - var exact = Columns.FirstOrDefault (c => c.PropertyName == propertyName); - return exact; - } - - public Column FindColumn (string columnName) - { - var exact = Columns.FirstOrDefault (c => c.Name == columnName); - return exact; - } - - PreparedSqlLiteInsertCommand _insertCommand; - string _insertCommandExtra; - - public PreparedSqlLiteInsertCommand GetInsertCommand(SQLiteConnection conn, string extra) - { - if (_insertCommand == null) { - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - else if (_insertCommandExtra != extra) { - _insertCommand.Dispose(); - _insertCommand = CreateInsertCommand(conn, extra); - _insertCommandExtra = extra; - } - return _insertCommand; - } - - PreparedSqlLiteInsertCommand CreateInsertCommand(SQLiteConnection conn, string extra) - { - var cols = InsertColumns; - string insertSql; - if (!cols.Any() && Columns.Count() == 1 && Columns[0].IsAutoInc) - { - insertSql = string.Format("insert {1} into \"{0}\" default values", TableName, extra); - } - else - { - var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0; - - if (replacing) { - cols = InsertOrReplaceColumns; - } - - insertSql = string.Format("insert {3} into \"{0}\"({1}) values ({2})", TableName, - string.Join(",", (from c in cols - select "\"" + c.Name + "\"").ToArray()), - string.Join(",", (from c in cols - select "?").ToArray()), extra); - - } - - var insertCommand = new PreparedSqlLiteInsertCommand(conn); - insertCommand.CommandText = insertSql; - return insertCommand; - } - - protected internal void Dispose() - { - if (_insertCommand != null) { - _insertCommand.Dispose(); - _insertCommand = null; - } - } - - public class Column - { - PropertyInfo _prop; - - public string Name { get; private set; } - - public string PropertyName { get { return _prop.Name; } } - - public Type ColumnType { get; private set; } - - public string Collation { get; private set; } - - public bool IsAutoInc { get; private set; } - public bool IsAutoGuid { get; private set; } - - public bool IsPK { get; private set; } - - public IEnumerable Indices { get; set; } - - public bool IsNullable { get; private set; } - - public int? MaxStringLength { get; private set; } - - public Column(PropertyInfo prop, CreateFlags createFlags = CreateFlags.None) - { - var colAttr = (ColumnAttribute)prop.GetCustomAttributes(typeof(ColumnAttribute), true).FirstOrDefault(); - - _prop = prop; - Name = colAttr == null ? prop.Name : colAttr.Name; - //If this type is Nullable then Nullable.GetUnderlyingType returns the T, otherwise it returns null, so get the actual type instead - ColumnType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; - Collation = Orm.Collation(prop); - - IsPK = Orm.IsPK(prop) || - (((createFlags & CreateFlags.ImplicitPK) == CreateFlags.ImplicitPK) && - string.Compare (prop.Name, Orm.ImplicitPkName, StringComparison.OrdinalIgnoreCase) == 0); - - var isAuto = Orm.IsAutoInc(prop) || (IsPK && ((createFlags & CreateFlags.AutoIncPK) == CreateFlags.AutoIncPK)); - IsAutoGuid = isAuto && ColumnType == typeof(Guid); - IsAutoInc = isAuto && !IsAutoGuid; - - Indices = Orm.GetIndices(prop); - if (!Indices.Any() - && !IsPK - && ((createFlags & CreateFlags.ImplicitIndex) == CreateFlags.ImplicitIndex) - && Name.EndsWith (Orm.ImplicitIndexSuffix, StringComparison.OrdinalIgnoreCase) - ) - { - Indices = new IndexedAttribute[] { new IndexedAttribute() }; - } - IsNullable = !(IsPK || Orm.IsMarkedNotNull(prop)); - MaxStringLength = Orm.MaxStringLength(prop); - } - - public void SetValue (object obj, object val) - { - _prop.SetValue (obj, val, null); - } - - public object GetValue (object obj) - { - return _prop.GetValue (obj, null); - } - } - } - - public static class Orm - { - public const int DefaultMaxStringLength = 140; - public const string ImplicitPkName = "Id"; - public const string ImplicitIndexSuffix = "Id"; - - public static string SqlDecl (TableMapping.Column p, bool storeDateTimeAsTicks) - { - string decl = "\"" + p.Name + "\" " + SqlType (p, storeDateTimeAsTicks) + " "; - - if (p.IsPK) { - decl += "primary key "; - } - if (p.IsAutoInc) { - decl += "autoincrement "; - } - if (!p.IsNullable) { - decl += "not null "; - } - if (!string.IsNullOrEmpty (p.Collation)) { - decl += "collate " + p.Collation + " "; - } - - return decl; - } - - public static string SqlType (TableMapping.Column p, bool storeDateTimeAsTicks) - { - var clrType = p.ColumnType; - if (clrType == typeof(Boolean) || clrType == typeof(Byte) || clrType == typeof(UInt16) || clrType == typeof(SByte) || clrType == typeof(Int16) || clrType == typeof(Int32)) { - return "integer"; - } else if (clrType == typeof(UInt32) || clrType == typeof(Int64)) { - return "bigint"; - } else if (clrType == typeof(Single) || clrType == typeof(Double) || clrType == typeof(Decimal)) { - return "float"; - } else if (clrType == typeof(String)) { - int? len = p.MaxStringLength; - - if (len.HasValue) - return "varchar(" + len.Value + ")"; - - return "varchar"; - } else if (clrType == typeof(TimeSpan)) { - return "bigint"; - } else if (clrType == typeof(DateTime)) { - return storeDateTimeAsTicks ? "bigint" : "datetime"; - } else if (clrType == typeof(DateTimeOffset)) { - return "bigint"; -#if !NETFX_CORE - } else if (clrType.IsEnum) { -#else - } else if (clrType.GetTypeInfo().IsEnum) { -#endif - return "integer"; - } else if (clrType == typeof(byte[])) { - return "blob"; - } else if (clrType == typeof(Guid)) { - return "varchar(36)"; - } else { - throw new NotSupportedException ("Don't know about " + clrType); - } - } - - public static bool IsPK (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(PrimaryKeyAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - - public static string Collation (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(CollationAttribute), true); -#if !NETFX_CORE - if (attrs.Length > 0) { - return ((CollationAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) { - return ((CollationAttribute)attrs.First()).Value; -#endif - } else { - return string.Empty; - } - } - - public static bool IsAutoInc (MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof(AutoIncrementAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - - public static IEnumerable GetIndices(MemberInfo p) - { - var attrs = p.GetCustomAttributes(typeof(IndexedAttribute), true); - return attrs.Cast(); - } - - public static int? MaxStringLength(PropertyInfo p) - { - var attrs = p.GetCustomAttributes (typeof(MaxLengthAttribute), true); -#if !NETFX_CORE - if (attrs.Length > 0) - return ((MaxLengthAttribute)attrs [0]).Value; -#else - if (attrs.Count() > 0) - return ((MaxLengthAttribute)attrs.First()).Value; -#endif - - return null; - } - - public static bool IsMarkedNotNull(MemberInfo p) - { - var attrs = p.GetCustomAttributes (typeof (NotNullAttribute), true); -#if !NETFX_CORE - return attrs.Length > 0; -#else - return attrs.Count() > 0; -#endif - } - } - - public partial class SQLiteCommand - { - SQLiteConnection _conn; - private List _bindings; - - public string CommandText { get; set; } - - internal SQLiteCommand (SQLiteConnection conn) - { - _conn = conn; - _bindings = new List (); - CommandText = ""; - } - - public int ExecuteNonQuery () - { - if (_conn.Trace) { - Debug.WriteLine ("Executing: " + this); - } - - var r = SQLite3.Result.OK; - var stmt = Prepare (); - r = SQLite3.Step (stmt); - Finalize (stmt); - if (r == SQLite3.Result.Done) { - int rowsAffected = SQLite3.Changes (_conn.Handle); - return rowsAffected; - } else if (r == SQLite3.Result.Error) { - string msg = SQLite3.GetErrmsg (_conn.Handle); - throw SQLiteException.New (r, msg); - } - else if (r == SQLite3.Result.Constraint) { - if (SQLite3.ExtendedErrCode (_conn.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - } - } - - throw SQLiteException.New(r, r.ToString()); - } - - public IEnumerable ExecuteDeferredQuery () - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))); - } - - public List ExecuteQuery () - { - return ExecuteDeferredQuery(_conn.GetMapping(typeof(T))).ToList(); - } - - public List ExecuteQuery (TableMapping map) - { - return ExecuteDeferredQuery(map).ToList(); - } - - /// - /// Invoked every time an instance is loaded from the database. - /// - /// - /// The newly created object. - /// - /// - /// This can be overridden in combination with the - /// method to hook into the life-cycle of objects. - /// - /// Type safety is not possible because MonoTouch does not support virtual generic methods. - /// - protected virtual void OnInstanceCreated (object obj) - { - // Can be overridden. - } - - public IEnumerable ExecuteDeferredQuery (TableMapping map) - { - if (_conn.Trace) { - Debug.WriteLine ("Executing Query: " + this); - } - - var stmt = Prepare (); - try - { - var cols = new TableMapping.Column[SQLite3.ColumnCount (stmt)]; - - for (int i = 0; i < cols.Length; i++) { - var name = SQLite3.ColumnName16 (stmt, i); - cols [i] = map.FindColumn (name); - } - - while (SQLite3.Step (stmt) == SQLite3.Result.Row) { - var obj = Activator.CreateInstance(map.MappedType); - for (int i = 0; i < cols.Length; i++) { - if (cols [i] == null) - continue; - var colType = SQLite3.ColumnType (stmt, i); - var val = ReadCol (stmt, i, colType, cols [i].ColumnType); - cols [i].SetValue (obj, val); - } - OnInstanceCreated (obj); - yield return (T)obj; - } - } - finally - { - SQLite3.Finalize(stmt); - } - } - - public T ExecuteScalar () - { - if (_conn.Trace) { - Debug.WriteLine ("Executing Query: " + this); - } - - T val = default(T); - - var stmt = Prepare (); - - try - { - var r = SQLite3.Step (stmt); - if (r == SQLite3.Result.Row) { - var colType = SQLite3.ColumnType (stmt, 0); - val = (T)ReadCol (stmt, 0, colType, typeof(T)); - } - else if (r == SQLite3.Result.Done) { - } - else - { - throw SQLiteException.New (r, SQLite3.GetErrmsg (_conn.Handle)); - } - } - finally - { - Finalize (stmt); - } - - return val; - } - - public void Bind (string name, object val) - { - _bindings.Add (new Binding { - Name = name, - Value = val - }); - } - - public void Bind (object val) - { - Bind (null, val); - } - - public override string ToString () - { - var parts = new string[1 + _bindings.Count]; - parts [0] = CommandText; - var i = 1; - foreach (var b in _bindings) { - parts [i] = string.Format (" {0}: {1}", i - 1, b.Value); - i++; - } - return string.Join (Environment.NewLine, parts); - } - - Sqlite3Statement Prepare() - { - var stmt = SQLite3.Prepare2 (_conn.Handle, CommandText); - BindAll (stmt); - return stmt; - } - - void Finalize (Sqlite3Statement stmt) - { - SQLite3.Finalize (stmt); - } - - void BindAll (Sqlite3Statement stmt) - { - int nextIdx = 1; - foreach (var b in _bindings) { - if (b.Name != null) { - b.Index = SQLite3.BindParameterIndex (stmt, b.Name); - } else { - b.Index = nextIdx++; - } - - BindParameter (stmt, b.Index, b.Value, _conn.StoreDateTimeAsTicks); - } - } - - internal static IntPtr NegativePointer = new IntPtr (-1); - - internal static void BindParameter (Sqlite3Statement stmt, int index, object value, bool storeDateTimeAsTicks) - { - if (value == null) { - SQLite3.BindNull (stmt, index); - } else { - if (value is Int32) { - SQLite3.BindInt (stmt, index, (int)value); - } else if (value is String) { - SQLite3.BindText (stmt, index, (string)value, -1, NegativePointer); - } else if (value is Byte || value is UInt16 || value is SByte || value is Int16) { - SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); - } else if (value is Boolean) { - SQLite3.BindInt (stmt, index, (bool)value ? 1 : 0); - } else if (value is UInt32 || value is Int64) { - SQLite3.BindInt64 (stmt, index, Convert.ToInt64 (value)); - } else if (value is Single || value is Double || value is Decimal) { - SQLite3.BindDouble (stmt, index, Convert.ToDouble (value)); - } else if (value is TimeSpan) { - SQLite3.BindInt64(stmt, index, ((TimeSpan)value).Ticks); - } else if (value is DateTime) { - if (storeDateTimeAsTicks) { - SQLite3.BindInt64 (stmt, index, ((DateTime)value).Ticks); - } - else { - SQLite3.BindText (stmt, index, ((DateTime)value).ToString ("yyyy-MM-dd HH:mm:ss"), -1, NegativePointer); - } - } else if (value is DateTimeOffset) { - SQLite3.BindInt64 (stmt, index, ((DateTimeOffset)value).UtcTicks); -#if !NETFX_CORE - } else if (value.GetType().IsEnum) { -#else - } else if (value.GetType().GetTypeInfo().IsEnum) { -#endif - SQLite3.BindInt (stmt, index, Convert.ToInt32 (value)); - } else if (value is byte[]){ - SQLite3.BindBlob(stmt, index, (byte[]) value, ((byte[]) value).Length, NegativePointer); - } else if (value is Guid) { - SQLite3.BindText(stmt, index, ((Guid)value).ToString(), 72, NegativePointer); - } else { - throw new NotSupportedException("Cannot store type: " + value.GetType()); - } - } - } - - class Binding - { - public string Name { get; set; } - - public object Value { get; set; } - - public int Index { get; set; } - } - - object ReadCol (Sqlite3Statement stmt, int index, SQLite3.ColType type, Type clrType) - { - if (type == SQLite3.ColType.Null) { - return null; - } else { - if (clrType == typeof(String)) { - return SQLite3.ColumnString (stmt, index); - } else if (clrType == typeof(Int32)) { - return (int)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Boolean)) { - return SQLite3.ColumnInt (stmt, index) == 1; - } else if (clrType == typeof(double)) { - return SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(float)) { - return (float)SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(TimeSpan)) { - return new TimeSpan(SQLite3.ColumnInt64(stmt, index)); - } else if (clrType == typeof(DateTime)) { - if (_conn.StoreDateTimeAsTicks) { - return new DateTime (SQLite3.ColumnInt64 (stmt, index)); - } - else { - var text = SQLite3.ColumnString (stmt, index); - return DateTime.Parse (text); - } - } else if (clrType == typeof(DateTimeOffset)) { - return new DateTimeOffset(SQLite3.ColumnInt64 (stmt, index),TimeSpan.Zero); -#if !NETFX_CORE - } else if (clrType.IsEnum) { -#else - } else if (clrType.GetTypeInfo().IsEnum) { -#endif - return SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Int64)) { - return SQLite3.ColumnInt64 (stmt, index); - } else if (clrType == typeof(UInt32)) { - return (uint)SQLite3.ColumnInt64 (stmt, index); - } else if (clrType == typeof(decimal)) { - return (decimal)SQLite3.ColumnDouble (stmt, index); - } else if (clrType == typeof(Byte)) { - return (byte)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(UInt16)) { - return (ushort)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(Int16)) { - return (short)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(sbyte)) { - return (sbyte)SQLite3.ColumnInt (stmt, index); - } else if (clrType == typeof(byte[])) { - return SQLite3.ColumnByteArray (stmt, index); - } else if (clrType == typeof(Guid)) { - var text = SQLite3.ColumnString(stmt, index); - return new Guid(text); - } else{ - throw new NotSupportedException ("Don't know how to read " + clrType); - } - } - } - } - - /// - /// Since the insert never changed, we only need to prepare once. - /// - public class PreparedSqlLiteInsertCommand : IDisposable - { - public bool Initialized { get; set; } - - protected SQLiteConnection Connection { get; set; } - - public string CommandText { get; set; } - - protected Sqlite3Statement Statement { get; set; } - internal static readonly Sqlite3Statement NullStatement = default(Sqlite3Statement); - - internal PreparedSqlLiteInsertCommand (SQLiteConnection conn) - { - Connection = conn; - } - - public int ExecuteNonQuery (object[] source) - { - if (Connection.Trace) { - Debug.WriteLine ("Executing: " + CommandText); - } - - var r = SQLite3.Result.OK; - - if (!Initialized) { - Statement = Prepare (); - Initialized = true; - } - - //bind the values. - if (source != null) { - for (int i = 0; i < source.Length; i++) { - SQLiteCommand.BindParameter (Statement, i + 1, source [i], Connection.StoreDateTimeAsTicks); - } - } - r = SQLite3.Step (Statement); - - if (r == SQLite3.Result.Done) { - int rowsAffected = SQLite3.Changes (Connection.Handle); - SQLite3.Reset (Statement); - return rowsAffected; - } else if (r == SQLite3.Result.Error) { - string msg = SQLite3.GetErrmsg (Connection.Handle); - SQLite3.Reset (Statement); - throw SQLiteException.New (r, msg); - } else if (r == SQLite3.Result.Constraint && SQLite3.ExtendedErrCode (Connection.Handle) == SQLite3.ExtendedResult.ConstraintNotNull) { - SQLite3.Reset (Statement); - throw NotNullConstraintViolationException.New (r, SQLite3.GetErrmsg (Connection.Handle)); - } else { - SQLite3.Reset (Statement); - throw SQLiteException.New (r, r.ToString ()); - } - } - - protected virtual Sqlite3Statement Prepare () - { - var stmt = SQLite3.Prepare2 (Connection.Handle, CommandText); - return stmt; - } - - public void Dispose () - { - Dispose (true); - GC.SuppressFinalize (this); - } - - private void Dispose (bool disposing) - { - if (Statement != NullStatement) { - try { - SQLite3.Finalize (Statement); - } finally { - Statement = NullStatement; - Connection = null; - } - } - } - - ~PreparedSqlLiteInsertCommand () - { - Dispose (false); - } - } - - public abstract class BaseTableQuery - { - protected class Ordering - { - public string ColumnName { get; set; } - public bool Ascending { get; set; } - } - } - - public class TableQuery : BaseTableQuery, IEnumerable - { - public SQLiteConnection Connection { get; private set; } - - public TableMapping Table { get; private set; } - - Expression _where; - List _orderBys; - int? _limit; - int? _offset; - - BaseTableQuery _joinInner; - Expression _joinInnerKeySelector; - BaseTableQuery _joinOuter; - Expression _joinOuterKeySelector; - Expression _joinSelector; - - Expression _selector; - - TableQuery (SQLiteConnection conn, TableMapping table) - { - Connection = conn; - Table = table; - } - - public TableQuery (SQLiteConnection conn) - { - Connection = conn; - Table = Connection.GetMapping (typeof(T)); - } - - public TableQuery Clone () - { - var q = new TableQuery (Connection, Table); - q._where = _where; - q._deferred = _deferred; - if (_orderBys != null) { - q._orderBys = new List (_orderBys); - } - q._limit = _limit; - q._offset = _offset; - q._joinInner = _joinInner; - q._joinInnerKeySelector = _joinInnerKeySelector; - q._joinOuter = _joinOuter; - q._joinOuterKeySelector = _joinOuterKeySelector; - q._joinSelector = _joinSelector; - q._selector = _selector; - return q; - } - - public TableQuery Where (Expression> predExpr) - { - if (predExpr.NodeType == ExpressionType.Lambda) { - var lambda = (LambdaExpression)predExpr; - var pred = lambda.Body; - var q = Clone (); - q.AddWhere (pred); - return q; - } else { - throw new NotSupportedException ("Must be a predicate"); - } - } - - public TableQuery Take (int n) - { - var q = Clone (); - q._limit = n; - return q; - } - - public TableQuery Skip (int n) - { - var q = Clone (); - q._offset = n; - return q; - } - - public T ElementAt (int index) - { - return Skip (index).Take (1).First (); - } - - bool _deferred; - public TableQuery Deferred () - { - var q = Clone (); - q._deferred = true; - return q; - } - - public TableQuery OrderBy (Expression> orderExpr) - { - return AddOrderBy (orderExpr, true); - } - - public TableQuery OrderByDescending (Expression> orderExpr) - { - return AddOrderBy (orderExpr, false); - } - - public TableQuery ThenBy(Expression> orderExpr) - { - return AddOrderBy(orderExpr, true); - } - - public TableQuery ThenByDescending(Expression> orderExpr) - { - return AddOrderBy(orderExpr, false); - } - - private TableQuery AddOrderBy (Expression> orderExpr, bool asc) - { - if (orderExpr.NodeType == ExpressionType.Lambda) { - var lambda = (LambdaExpression)orderExpr; - - MemberExpression mem = null; - - var unary = lambda.Body as UnaryExpression; - if (unary != null && unary.NodeType == ExpressionType.Convert) { - mem = unary.Operand as MemberExpression; - } - else { - mem = lambda.Body as MemberExpression; - } - - if (mem != null && (mem.Expression.NodeType == ExpressionType.Parameter)) { - var q = Clone (); - if (q._orderBys == null) { - q._orderBys = new List (); - } - q._orderBys.Add (new Ordering { - ColumnName = Table.FindColumnWithPropertyName(mem.Member.Name).Name, - Ascending = asc - }); - return q; - } else { - throw new NotSupportedException ("Order By does not support: " + orderExpr); - } - } else { - throw new NotSupportedException ("Must be a predicate"); - } - } - - private void AddWhere (Expression pred) - { - if (_where == null) { - _where = pred; - } else { - _where = Expression.AndAlso (_where, pred); - } - } - - public TableQuery Join ( - TableQuery inner, - Expression> outerKeySelector, - Expression> innerKeySelector, - Expression> resultSelector) - { - var q = new TableQuery (Connection, Connection.GetMapping (typeof (TResult))) { - _joinOuter = this, - _joinOuterKeySelector = outerKeySelector, - _joinInner = inner, - _joinInnerKeySelector = innerKeySelector, - _joinSelector = resultSelector, - }; - return q; - } - - public TableQuery Select (Expression> selector) - { - var q = Clone (); - q._selector = selector; - return q; - } - - private SQLiteCommand GenerateCommand (string selectionList) - { - if (_joinInner != null && _joinOuter != null) { - throw new NotSupportedException ("Joins are not supported."); - } - else { - var cmdText = "select " + selectionList + " from \"" + Table.TableName + "\""; - var args = new List (); - if (_where != null) { - var w = CompileExpr (_where, args); - cmdText += " where " + w.CommandText; - } - if ((_orderBys != null) && (_orderBys.Count > 0)) { - var t = string.Join (", ", _orderBys.Select (o => "\"" + o.ColumnName + "\"" + (o.Ascending ? "" : " desc")).ToArray ()); - cmdText += " order by " + t; - } - if (_limit.HasValue) { - cmdText += " limit " + _limit.Value; - } - if (_offset.HasValue) { - if (!_limit.HasValue) { - cmdText += " limit -1 "; - } - cmdText += " offset " + _offset.Value; - } - return Connection.CreateCommand (cmdText, args.ToArray ()); - } - } - - class CompileResult - { - public string CommandText { get; set; } - - public object Value { get; set; } - } - - private CompileResult CompileExpr (Expression expr, List queryArgs) - { - if (expr == null) { - throw new NotSupportedException ("Expression is NULL"); - } else if (expr is BinaryExpression) { - var bin = (BinaryExpression)expr; - - var leftr = CompileExpr (bin.Left, queryArgs); - var rightr = CompileExpr (bin.Right, queryArgs); - - //If either side is a parameter and is null, then handle the other side specially (for "is null"/"is not null") - string text; - if (leftr.CommandText == "?" && leftr.Value == null) - text = CompileNullBinaryExpression(bin, rightr); - else if (rightr.CommandText == "?" && rightr.Value == null) - text = CompileNullBinaryExpression(bin, leftr); - else - text = "(" + leftr.CommandText + " " + GetSqlName(bin) + " " + rightr.CommandText + ")"; - return new CompileResult { CommandText = text }; - } else if (expr.NodeType == ExpressionType.Call) { - - var call = (MethodCallExpression)expr; - var args = new CompileResult[call.Arguments.Count]; - var obj = call.Object != null ? CompileExpr (call.Object, queryArgs) : null; - - for (var i = 0; i < args.Length; i++) { - args [i] = CompileExpr (call.Arguments [i], queryArgs); - } - - var sqlCall = ""; - - if (call.Method.Name == "Like" && args.Length == 2) { - sqlCall = "(" + args [0].CommandText + " like " + args [1].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 2) { - sqlCall = "(" + args [1].CommandText + " in " + args [0].CommandText + ")"; - } - else if (call.Method.Name == "Contains" && args.Length == 1) { - if (call.Object != null && call.Object.Type == typeof(string)) { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args [0].CommandText + " || '%'))"; - } - else { - sqlCall = "(" + args [0].CommandText + " in " + obj.CommandText + ")"; - } - } - else if (call.Method.Name == "StartsWith" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " like (" + args [0].CommandText + " || '%'))"; - } - else if (call.Method.Name == "EndsWith" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " like ('%' || " + args [0].CommandText + "))"; - } - else if (call.Method.Name == "Equals" && args.Length == 1) { - sqlCall = "(" + obj.CommandText + " = (" + args[0].CommandText + "))"; - } else if (call.Method.Name == "ToLower") { - sqlCall = "(lower(" + obj.CommandText + "))"; - } else if (call.Method.Name == "ToUpper") { - sqlCall = "(upper(" + obj.CommandText + "))"; - } else { - sqlCall = call.Method.Name.ToLower () + "(" + string.Join (",", args.Select (a => a.CommandText).ToArray ()) + ")"; - } - return new CompileResult { CommandText = sqlCall }; - - } else if (expr.NodeType == ExpressionType.Constant) { - var c = (ConstantExpression)expr; - queryArgs.Add (c.Value); - return new CompileResult { - CommandText = "?", - Value = c.Value - }; - } else if (expr.NodeType == ExpressionType.Convert) { - var u = (UnaryExpression)expr; - var ty = u.Type; - var valr = CompileExpr (u.Operand, queryArgs); - return new CompileResult { - CommandText = valr.CommandText, - Value = valr.Value != null ? ConvertTo (valr.Value, ty) : null - }; - } else if (expr.NodeType == ExpressionType.MemberAccess) { - var mem = (MemberExpression)expr; - - if (mem.Expression!=null && mem.Expression.NodeType == ExpressionType.Parameter) { - // - // This is a column of our table, output just the column name - // Need to translate it if that column name is mapped - // - var columnName = Table.FindColumnWithPropertyName (mem.Member.Name).Name; - return new CompileResult { CommandText = "\"" + columnName + "\"" }; - } else { - object obj = null; - if (mem.Expression != null) { - var r = CompileExpr (mem.Expression, queryArgs); - if (r.Value == null) { - throw new NotSupportedException ("Member access failed to compile expression"); - } - if (r.CommandText == "?") { - queryArgs.RemoveAt (queryArgs.Count - 1); - } - obj = r.Value; - } - - // - // Get the member value - // - object val = null; - -#if !NETFX_CORE - if (mem.Member.MemberType == MemberTypes.Property) { -#else - if (mem.Member is PropertyInfo) { -#endif - var m = (PropertyInfo)mem.Member; - val = m.GetValue (obj, null); -#if !NETFX_CORE - } else if (mem.Member.MemberType == MemberTypes.Field) { -#else - } else if (mem.Member is FieldInfo) { -#endif -#if SILVERLIGHT - val = Expression.Lambda (expr).Compile ().DynamicInvoke (); -#else - var m = (FieldInfo)mem.Member; - val = m.GetValue (obj); -#endif - } else { -#if !NETFX_CORE - throw new NotSupportedException ("MemberExpr: " + mem.Member.MemberType); -#else - throw new NotSupportedException ("MemberExpr: " + mem.Member.DeclaringType); -#endif - } - - // - // Work special magic for enumerables - // - if (val != null && val is System.Collections.IEnumerable && !(val is string) && !(val is System.Collections.Generic.IEnumerable)) { - var sb = new System.Text.StringBuilder(); - sb.Append("("); - var head = ""; - foreach (var a in (System.Collections.IEnumerable)val) { - queryArgs.Add(a); - sb.Append(head); - sb.Append("?"); - head = ","; - } - sb.Append(")"); - return new CompileResult { - CommandText = sb.ToString(), - Value = val - }; - } - else { - queryArgs.Add (val); - return new CompileResult { - CommandText = "?", - Value = val - }; - } - } - } - throw new NotSupportedException ("Cannot compile: " + expr.NodeType.ToString ()); - } - - static object ConvertTo (object obj, Type t) - { - Type nut = Nullable.GetUnderlyingType(t); - - if (nut != null) { - if (obj == null) return null; - return Convert.ChangeType (obj, nut); - } else { - return Convert.ChangeType (obj, t); - } - } - - /// - /// Compiles a BinaryExpression where one of the parameters is null. - /// - /// The non-null parameter - private string CompileNullBinaryExpression(BinaryExpression expression, CompileResult parameter) - { - if (expression.NodeType == ExpressionType.Equal) - return "(" + parameter.CommandText + " is ?)"; - else if (expression.NodeType == ExpressionType.NotEqual) - return "(" + parameter.CommandText + " is not ?)"; - else - throw new NotSupportedException("Cannot compile Null-BinaryExpression with type " + expression.NodeType.ToString()); - } - - string GetSqlName (Expression expr) - { - var n = expr.NodeType; - if (n == ExpressionType.GreaterThan) - return ">"; else if (n == ExpressionType.GreaterThanOrEqual) { - return ">="; - } else if (n == ExpressionType.LessThan) { - return "<"; - } else if (n == ExpressionType.LessThanOrEqual) { - return "<="; - } else if (n == ExpressionType.And) { - return "&"; - } else if (n == ExpressionType.AndAlso) { - return "and"; - } else if (n == ExpressionType.Or) { - return "|"; - } else if (n == ExpressionType.OrElse) { - return "or"; - } else if (n == ExpressionType.Equal) { - return "="; - } else if (n == ExpressionType.NotEqual) { - return "!="; - } else { - throw new NotSupportedException ("Cannot get SQL for: " + n); - } - } - - public int Count () - { - return GenerateCommand("count(*)").ExecuteScalar (); - } - - public int Count (Expression> predExpr) - { - return Where (predExpr).Count (); - } - - public IEnumerator GetEnumerator () - { - if (!_deferred) - return GenerateCommand("*").ExecuteQuery().GetEnumerator(); - - return GenerateCommand("*").ExecuteDeferredQuery().GetEnumerator(); - } - - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () - { - return GetEnumerator (); - } - - public T First () - { - var query = Take (1); - return query.ToList().First (); - } - - public T FirstOrDefault () - { - var query = Take (1); - return query.ToList().FirstOrDefault (); - } - } - - public static class SQLite3 - { - public enum Result : int - { - OK = 0, - Error = 1, - Internal = 2, - Perm = 3, - Abort = 4, - Busy = 5, - Locked = 6, - NoMem = 7, - ReadOnly = 8, - Interrupt = 9, - IOError = 10, - Corrupt = 11, - NotFound = 12, - Full = 13, - CannotOpen = 14, - LockErr = 15, - Empty = 16, - SchemaChngd = 17, - TooBig = 18, - Constraint = 19, - Mismatch = 20, - Misuse = 21, - NotImplementedLFS = 22, - AccessDenied = 23, - Format = 24, - Range = 25, - NonDBFile = 26, - Notice = 27, - Warning = 28, - Row = 100, - Done = 101 - } - - public enum ExtendedResult : int - { - IOErrorRead = (Result.IOError | (1 << 8)), - IOErrorShortRead = (Result.IOError | (2 << 8)), - IOErrorWrite = (Result.IOError | (3 << 8)), - IOErrorFsync = (Result.IOError | (4 << 8)), - IOErrorDirFSync = (Result.IOError | (5 << 8)), - IOErrorTruncate = (Result.IOError | (6 << 8)), - IOErrorFStat = (Result.IOError | (7 << 8)), - IOErrorUnlock = (Result.IOError | (8 << 8)), - IOErrorRdlock = (Result.IOError | (9 << 8)), - IOErrorDelete = (Result.IOError | (10 << 8)), - IOErrorBlocked = (Result.IOError | (11 << 8)), - IOErrorNoMem = (Result.IOError | (12 << 8)), - IOErrorAccess = (Result.IOError | (13 << 8)), - IOErrorCheckReservedLock = (Result.IOError | (14 << 8)), - IOErrorLock = (Result.IOError | (15 << 8)), - IOErrorClose = (Result.IOError | (16 << 8)), - IOErrorDirClose = (Result.IOError | (17 << 8)), - IOErrorSHMOpen = (Result.IOError | (18 << 8)), - IOErrorSHMSize = (Result.IOError | (19 << 8)), - IOErrorSHMLock = (Result.IOError | (20 << 8)), - IOErrorSHMMap = (Result.IOError | (21 << 8)), - IOErrorSeek = (Result.IOError | (22 << 8)), - IOErrorDeleteNoEnt = (Result.IOError | (23 << 8)), - IOErrorMMap = (Result.IOError | (24 << 8)), - LockedSharedcache = (Result.Locked | (1 << 8)), - BusyRecovery = (Result.Busy | (1 << 8)), - CannottOpenNoTempDir = (Result.CannotOpen | (1 << 8)), - CannotOpenIsDir = (Result.CannotOpen | (2 << 8)), - CannotOpenFullPath = (Result.CannotOpen | (3 << 8)), - CorruptVTab = (Result.Corrupt | (1 << 8)), - ReadonlyRecovery = (Result.ReadOnly | (1 << 8)), - ReadonlyCannotLock = (Result.ReadOnly | (2 << 8)), - ReadonlyRollback = (Result.ReadOnly | (3 << 8)), - AbortRollback = (Result.Abort | (2 << 8)), - ConstraintCheck = (Result.Constraint | (1 << 8)), - ConstraintCommitHook = (Result.Constraint | (2 << 8)), - ConstraintForeignKey = (Result.Constraint | (3 << 8)), - ConstraintFunction = (Result.Constraint | (4 << 8)), - ConstraintNotNull = (Result.Constraint | (5 << 8)), - ConstraintPrimaryKey = (Result.Constraint | (6 << 8)), - ConstraintTrigger = (Result.Constraint | (7 << 8)), - ConstraintUnique = (Result.Constraint | (8 << 8)), - ConstraintVTab = (Result.Constraint | (9 << 8)), - NoticeRecoverWAL = (Result.Notice | (1 << 8)), - NoticeRecoverRollback = (Result.Notice | (2 << 8)) - } - - - public enum ConfigOption : int - { - SingleThread = 1, - MultiThread = 2, - Serialized = 3 - } - -#if !USE_CSHARP_SQLITE && !USE_WP8_NATIVE_SQLITE - [DllImport("sqlite3", EntryPoint = "sqlite3_open", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Open ([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open(byte[] filename, out IntPtr db, int flags, IntPtr zvfs); - - [DllImport("sqlite3", EntryPoint = "sqlite3_open16", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Open16([MarshalAs(UnmanagedType.LPWStr)] string filename, out IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_enable_load_extension", CallingConvention=CallingConvention.Cdecl)] - public static extern Result EnableLoadExtension (IntPtr db, int onoff); - - [DllImport("sqlite3", EntryPoint = "sqlite3_close", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Close (IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_initialize", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Initialize(); - - [DllImport("sqlite3", EntryPoint = "sqlite3_shutdown", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Shutdown(); - - [DllImport("sqlite3", EntryPoint = "sqlite3_config", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Config (ConfigOption option); - - [DllImport("sqlite3", EntryPoint = "sqlite3_win32_set_directory", CallingConvention=CallingConvention.Cdecl, CharSet=CharSet.Unicode)] - public static extern int SetDirectory (uint directoryType, string directoryPath); - - [DllImport("sqlite3", EntryPoint = "sqlite3_busy_timeout", CallingConvention=CallingConvention.Cdecl)] - public static extern Result BusyTimeout (IntPtr db, int milliseconds); - - [DllImport("sqlite3", EntryPoint = "sqlite3_changes", CallingConvention=CallingConvention.Cdecl)] - public static extern int Changes (IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Prepare2 (IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, int numBytes, out IntPtr stmt, IntPtr pzTail); - -#if NETFX_CORE - [DllImport ("sqlite3", EntryPoint = "sqlite3_prepare_v2", CallingConvention = CallingConvention.Cdecl)] - public static extern Result Prepare2 (IntPtr db, byte[] queryBytes, int numBytes, out IntPtr stmt, IntPtr pzTail); -#endif - - public static IntPtr Prepare2 (IntPtr db, string query) - { - IntPtr stmt; -#if NETFX_CORE - byte[] queryBytes = System.Text.UTF8Encoding.UTF8.GetBytes (query); - var r = Prepare2 (db, queryBytes, queryBytes.Length, out stmt, IntPtr.Zero); -#else - var r = Prepare2 (db, query, System.Text.UTF8Encoding.UTF8.GetByteCount (query), out stmt, IntPtr.Zero); -#endif - if (r != Result.OK) { - throw SQLiteException.New (r, GetErrmsg (db)); - } - return stmt; - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_step", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Step (IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_reset", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Reset (IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_finalize", CallingConvention=CallingConvention.Cdecl)] - public static extern Result Finalize (IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_last_insert_rowid", CallingConvention=CallingConvention.Cdecl)] - public static extern long LastInsertRowid (IntPtr db); - - [DllImport("sqlite3", EntryPoint = "sqlite3_errmsg16", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr Errmsg (IntPtr db); - - public static string GetErrmsg (IntPtr db) - { - return Marshal.PtrToStringUni (Errmsg (db)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_parameter_index", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindParameterIndex (IntPtr stmt, [MarshalAs(UnmanagedType.LPStr)] string name); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_null", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindNull (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindInt (IntPtr stmt, int index, int val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_int64", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindInt64 (IntPtr stmt, int index, long val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_double", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindDouble (IntPtr stmt, int index, double val); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_text16", CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - public static extern int BindText (IntPtr stmt, int index, [MarshalAs(UnmanagedType.LPWStr)] string val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_bind_blob", CallingConvention=CallingConvention.Cdecl)] - public static extern int BindBlob (IntPtr stmt, int index, byte[] val, int n, IntPtr free); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_count", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnCount (IntPtr stmt); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnName (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_name16", CallingConvention=CallingConvention.Cdecl)] - static extern IntPtr ColumnName16Internal (IntPtr stmt, int index); - public static string ColumnName16(IntPtr stmt, int index) - { - return Marshal.PtrToStringUni(ColumnName16Internal(stmt, index)); - } - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_type", CallingConvention=CallingConvention.Cdecl)] - public static extern ColType ColumnType (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnInt (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_int64", CallingConvention=CallingConvention.Cdecl)] - public static extern long ColumnInt64 (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_double", CallingConvention=CallingConvention.Cdecl)] - public static extern double ColumnDouble (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnText (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_text16", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnText16 (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_blob", CallingConvention=CallingConvention.Cdecl)] - public static extern IntPtr ColumnBlob (IntPtr stmt, int index); - - [DllImport("sqlite3", EntryPoint = "sqlite3_column_bytes", CallingConvention=CallingConvention.Cdecl)] - public static extern int ColumnBytes (IntPtr stmt, int index); - - public static string ColumnString (IntPtr stmt, int index) - { - return Marshal.PtrToStringUni (SQLite3.ColumnText16 (stmt, index)); - } - - public static byte[] ColumnByteArray (IntPtr stmt, int index) - { - int length = ColumnBytes (stmt, index); - var result = new byte[length]; - if (length > 0) - Marshal.Copy (ColumnBlob (stmt, index), result, 0, length); - return result; - } - - [DllImport ("sqlite3", EntryPoint = "sqlite3_extended_errcode", CallingConvention = CallingConvention.Cdecl)] - public static extern ExtendedResult ExtendedErrCode (IntPtr db); - - [DllImport ("sqlite3", EntryPoint = "sqlite3_libversion_number", CallingConvention = CallingConvention.Cdecl)] - public static extern int LibVersionNumber (); -#else - public static Result Open(string filename, out Sqlite3DatabaseHandle db) - { - return (Result) Sqlite3.sqlite3_open(filename, out db); - } - - public static Result Open(string filename, out Sqlite3DatabaseHandle db, int flags, IntPtr zVfs) - { -#if USE_WP8_NATIVE_SQLITE - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, ""); -#else - return (Result)Sqlite3.sqlite3_open_v2(filename, out db, flags, null); -#endif - } - - public static Result Close(Sqlite3DatabaseHandle db) - { - return (Result)Sqlite3.sqlite3_close(db); - } - - public static Result BusyTimeout(Sqlite3DatabaseHandle db, int milliseconds) - { - return (Result)Sqlite3.sqlite3_busy_timeout(db, milliseconds); - } - - public static int Changes(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_changes(db); - } - - public static Sqlite3Statement Prepare2(Sqlite3DatabaseHandle db, string query) - { - Sqlite3Statement stmt = default(Sqlite3Statement); -#if USE_WP8_NATIVE_SQLITE - var r = Sqlite3.sqlite3_prepare_v2(db, query, out stmt); -#else - stmt = new Sqlite3Statement(); - var r = Sqlite3.sqlite3_prepare_v2(db, query, -1, ref stmt, 0); -#endif - if (r != 0) - { - throw SQLiteException.New((Result)r, GetErrmsg(db)); - } - return stmt; - } - - public static Result Step(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_step(stmt); - } - - public static Result Reset(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_reset(stmt); - } - - public static Result Finalize(Sqlite3Statement stmt) - { - return (Result)Sqlite3.sqlite3_finalize(stmt); - } - - public static long LastInsertRowid(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_last_insert_rowid(db); - } - - public static string GetErrmsg(Sqlite3DatabaseHandle db) - { - return Sqlite3.sqlite3_errmsg(db); - } - - public static int BindParameterIndex(Sqlite3Statement stmt, string name) - { - return Sqlite3.sqlite3_bind_parameter_index(stmt, name); - } - - public static int BindNull(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_bind_null(stmt, index); - } - - public static int BindInt(Sqlite3Statement stmt, int index, int val) - { - return Sqlite3.sqlite3_bind_int(stmt, index, val); - } - - public static int BindInt64(Sqlite3Statement stmt, int index, long val) - { - return Sqlite3.sqlite3_bind_int64(stmt, index, val); - } - - public static int BindDouble(Sqlite3Statement stmt, int index, double val) - { - return Sqlite3.sqlite3_bind_double(stmt, index, val); - } - - public static int BindText(Sqlite3Statement stmt, int index, string val, int n, IntPtr free) - { -#if USE_WP8_NATIVE_SQLITE - return Sqlite3.sqlite3_bind_text(stmt, index, val, n); -#else - return Sqlite3.sqlite3_bind_text(stmt, index, val, n, null); -#endif - } - - public static int BindBlob(Sqlite3Statement stmt, int index, byte[] val, int n, IntPtr free) - { -#if USE_WP8_NATIVE_SQLITE - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n); -#else - return Sqlite3.sqlite3_bind_blob(stmt, index, val, n, null); -#endif - } - - public static int ColumnCount(Sqlite3Statement stmt) - { - return Sqlite3.sqlite3_column_count(stmt); - } - - public static string ColumnName(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static string ColumnName16(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_name(stmt, index); - } - - public static ColType ColumnType(Sqlite3Statement stmt, int index) - { - return (ColType)Sqlite3.sqlite3_column_type(stmt, index); - } - - public static int ColumnInt(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_int(stmt, index); - } - - public static long ColumnInt64(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_int64(stmt, index); - } - - public static double ColumnDouble(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_double(stmt, index); - } - - public static string ColumnText(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static string ColumnText16(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnBlob(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_blob(stmt, index); - } - - public static int ColumnBytes(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_bytes(stmt, index); - } - - public static string ColumnString(Sqlite3Statement stmt, int index) - { - return Sqlite3.sqlite3_column_text(stmt, index); - } - - public static byte[] ColumnByteArray(Sqlite3Statement stmt, int index) - { - return ColumnBlob(stmt, index); - } - - public static Result EnableLoadExtension(Sqlite3DatabaseHandle db, int onoff) - { - return (Result)Sqlite3.sqlite3_enable_load_extension(db, onoff); - } - - public static ExtendedResult ExtendedErrCode(Sqlite3DatabaseHandle db) - { - return (ExtendedResult)Sqlite3.sqlite3_extended_errcode(db); - } -#endif - - public enum ColType : int - { - Integer = 1, - Float = 2, - Text = 3, - Blob = 4, - Null = 5 - } - } -} diff --git a/NadekoBot/_Models/DataModels/AnnouncementModel.cs b/NadekoBot/_Models/DataModels/AnnouncementModel.cs deleted file mode 100644 index c4103dad..00000000 --- a/NadekoBot/_Models/DataModels/AnnouncementModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Newtonsoft.Json; - -namespace NadekoBot.DataModels -{ - internal class Announcement : IDataModel - { - public long ServerId { get; set; } = 0; - public bool Greet { get; set; } = false; - public bool GreetPM { get; set; } = false; - [JsonProperty("greetChannel")] - public long GreetChannelId { get; set; } = 0; - public string GreetText { get; set; } = "Welcome %user%!"; - public bool Bye { get; set; } = false; - public bool ByePM { get; set; } = false; - [JsonProperty("byeChannel")] - public long ByeChannelId { get; set; } = 0; - public string ByeText { get; set; } = "%user% has left the server."; - public bool DeleteGreetMessages { get; set; } = true; - } -} diff --git a/NadekoBot/_Models/DataModels/CommandModel.cs b/NadekoBot/_Models/DataModels/CommandModel.cs deleted file mode 100644 index 204ec061..00000000 --- a/NadekoBot/_Models/DataModels/CommandModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace NadekoBot.DataModels { - internal class Command : IDataModel { - public long UserId { get; set; } - public string UserName { get; set; } - public long ServerId { get; set; } - public string ServerName { get; set; } - public long ChannelId { get; set; } - public string ChannelName { get; set; } - public string CommandName { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/CurrencyStateModel.cs b/NadekoBot/_Models/DataModels/CurrencyStateModel.cs deleted file mode 100644 index 9c8b8671..00000000 --- a/NadekoBot/_Models/DataModels/CurrencyStateModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NadekoBot.DataModels { - internal class CurrencyState : IDataModel { - public long Value { get; set; } - [SQLite.Unique] - public long UserId { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/CurrencyTransactionModel.cs b/NadekoBot/_Models/DataModels/CurrencyTransactionModel.cs deleted file mode 100644 index 51fcccc3..00000000 --- a/NadekoBot/_Models/DataModels/CurrencyTransactionModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NadekoBot.DataModels { - internal class CurrencyTransaction : IDataModel { - public string Reason { get; set; } - public int Value { get; set; } - public long UserId { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/Donator.cs b/NadekoBot/_Models/DataModels/Donator.cs deleted file mode 100644 index 483898ec..00000000 --- a/NadekoBot/_Models/DataModels/Donator.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NadekoBot.DataModels { - internal class Donator : IDataModel { - public long UserId { get; set; } - public string UserName { get; set; } - public int Amount { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/IDataModel.cs b/NadekoBot/_Models/DataModels/IDataModel.cs deleted file mode 100644 index 90191474..00000000 --- a/NadekoBot/_Models/DataModels/IDataModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using SQLite; -using System; - -namespace NadekoBot.DataModels -{ - internal abstract class IDataModel - { - [PrimaryKey, AutoIncrement] - public int? Id { get; set; } - [Newtonsoft.Json.JsonProperty("createdAt")] - public DateTime DateAdded { get; set; } = DateTime.Now; - public IDataModel() { } - } -} diff --git a/NadekoBot/_Models/DataModels/Incident.cs b/NadekoBot/_Models/DataModels/Incident.cs deleted file mode 100644 index 2ce2ddd6..00000000 --- a/NadekoBot/_Models/DataModels/Incident.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NadekoBot.DataModels -{ - class Incident : IDataModel - { - public long ServerId { get; set; } - public long ChannelId { get; set; } - public string Text { get; set; } - public bool Read { get; set; } = false; - } -} diff --git a/NadekoBot/_Models/DataModels/MusicPlaylist.cs b/NadekoBot/_Models/DataModels/MusicPlaylist.cs deleted file mode 100644 index 60973baa..00000000 --- a/NadekoBot/_Models/DataModels/MusicPlaylist.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace NadekoBot.DataModels -{ - internal class MusicPlaylist : IDataModel - { - public string Name { get; set; } - public long CreatorId { get; set; } - public string CreatorName { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/PlaylistSongInfo.cs b/NadekoBot/_Models/DataModels/PlaylistSongInfo.cs deleted file mode 100644 index 84929781..00000000 --- a/NadekoBot/_Models/DataModels/PlaylistSongInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace NadekoBot.DataModels -{ - internal class PlaylistSongInfo : IDataModel - { - public int PlaylistId { get; set; } - public int SongInfoId { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/PokeTypes.cs b/NadekoBot/_Models/DataModels/PokeTypes.cs deleted file mode 100644 index d0f538af..00000000 --- a/NadekoBot/_Models/DataModels/PokeTypes.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.DataModels -{ - class UserPokeTypes : IDataModel - { - public long UserId { get; set; } - public string type { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/Reminder.cs b/NadekoBot/_Models/DataModels/Reminder.cs deleted file mode 100644 index 043700fd..00000000 --- a/NadekoBot/_Models/DataModels/Reminder.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace NadekoBot.DataModels -{ - class Reminder : IDataModel - { - public DateTime When { get; set; } - public long ChannelId { get; set; } - public long ServerId { get; set; } - public long UserId { get; set; } - public string Message { get; set; } - public bool IsPrivate { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/RequestModel.cs b/NadekoBot/_Models/DataModels/RequestModel.cs deleted file mode 100644 index 7b86198f..00000000 --- a/NadekoBot/_Models/DataModels/RequestModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NadekoBot.DataModels { - internal class Request : IDataModel { - public string UserName { get; set; } - public long UserId { get; set; } - public string ServerName { get; set; } - public long ServerId { get; set; } - [Newtonsoft.Json.JsonProperty("Request")] - public string RequestText { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/SongInfo.cs b/NadekoBot/_Models/DataModels/SongInfo.cs deleted file mode 100644 index 6fe980ac..00000000 --- a/NadekoBot/_Models/DataModels/SongInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using SQLite; - -namespace NadekoBot.DataModels -{ - internal class SongInfo : IDataModel - { - public string Provider { get; internal set; } - public int ProviderType { get; internal set; } - public string Title { get; internal set; } - public string Uri { get; internal set; } - [Unique] - public string Query { get; internal set; } - } -} diff --git a/NadekoBot/_Models/DataModels/StatsModel.cs b/NadekoBot/_Models/DataModels/StatsModel.cs deleted file mode 100644 index 480b569f..00000000 --- a/NadekoBot/_Models/DataModels/StatsModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace NadekoBot.DataModels { - internal class Stats : IDataModel { - public int ConnectedServers { get; set; } - public int OnlineUsers { get; set; } - public TimeSpan Uptime { get; set; } - public int RealOnlineUsers { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/TestDataModel.cs b/NadekoBot/_Models/DataModels/TestDataModel.cs deleted file mode 100644 index 247743ca..00000000 --- a/NadekoBot/_Models/DataModels/TestDataModel.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace NadekoBot.DataModels -{ - internal class TestDataModel : IDataModel - { - public long TestNumber { get; set; } - public string TestString { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/TypingArticleModel.cs b/NadekoBot/_Models/DataModels/TypingArticleModel.cs deleted file mode 100644 index 2557a51c..00000000 --- a/NadekoBot/_Models/DataModels/TypingArticleModel.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace NadekoBot.DataModels { - internal class TypingArticle : IDataModel { - public string Text { get; set; } - } -} diff --git a/NadekoBot/_Models/DataModels/UserQuoteModel.cs b/NadekoBot/_Models/DataModels/UserQuoteModel.cs deleted file mode 100644 index 544252cc..00000000 --- a/NadekoBot/_Models/DataModels/UserQuoteModel.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace NadekoBot.DataModels { - internal class UserQuote : IDataModel { - public string UserName { get; set; } - public string Keyword { get; set; } - public string Text { get; set; } - } -} diff --git a/NadekoBot/_Models/JSONModels/AnimeResult.cs b/NadekoBot/_Models/JSONModels/AnimeResult.cs deleted file mode 100644 index a092ae03..00000000 --- a/NadekoBot/_Models/JSONModels/AnimeResult.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NadekoBot.Classes.JSONModels -{ - public class AnimeResult - { - public int id; - public string airing_status; - public string title_english; - public int total_episodes; - public string description; - public string image_url_lge; - - public override string ToString() => - "`Title:` **" + title_english + - "**\n`Status:` " + airing_status + - "\n`Episodes:` " + total_episodes + - "\n`Link:` http://anilist.co/anime/" + id + - "\n`Synopsis:` " + description.Substring(0, description.Length > 500 ? 500 : description.Length) + "..." + - "\n`img:` " + image_url_lge; - } -} \ No newline at end of file diff --git a/NadekoBot/_Models/JSONModels/Configuration.cs b/NadekoBot/_Models/JSONModels/Configuration.cs deleted file mode 100644 index 499609ac..00000000 --- a/NadekoBot/_Models/JSONModels/Configuration.cs +++ /dev/null @@ -1,261 +0,0 @@ -using Discord; -using NadekoBot.Extensions; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization; -using System.Threading; -using System.Threading.Tasks; - -namespace NadekoBot.Classes.JSONModels -{ - public class Configuration - { - [JsonIgnore] - public static readonly Dictionary> DefaultCustomReactions = new Dictionary> - { - {@"\o\", new List() - { "/o/" } }, - {"/o/", new List() - { @"\o\" } }, - {"moveto", new List() { - @"(👉 ͡° ͜ʖ ͡°)👉 %target%" } }, - {"comeatmebro", new List() { - "%target% (ง’̀-‘́)ง" } }, - {"e", new List() { - "%user% did it 😒 🔫", - "%target% did it 😒 🔫" } }, - {"%mention% insult", new List() { - "%target% You are a poop.", - "%target% You're a jerk.", - "%target% I will eat you when I get my powers back." - } }, - {"%mention% praise", new List() - { - "%target% You are cool.", - "%target% You are nice!", - "%target% You did a good job.", - "%target% You did something nice.", - "%target% is awesome!", - "%target% Wow." - } }, - {"%mention% pat", new List() { - "http://i.imgur.com/IiQwK12.gif", - "http://i.imgur.com/JCXj8yD.gif", - "http://i.imgur.com/qqBl2bm.gif", - "http://i.imgur.com/eOJlnwP.gif", - "https://45.media.tumblr.com/229ec0458891c4dcd847545c81e760a5/tumblr_mpfy232F4j1rxrpjzo1_r2_500.gif", - "https://media.giphy.com/media/KZQlfylo73AMU/giphy.gif", - "https://media.giphy.com/media/12hvLuZ7uzvCvK/giphy.gif", - "http://gallery1.anivide.com/_full/65030_1382582341.gif", - "https://49.media.tumblr.com/8e8a099c4eba22abd3ec0f70fd087cce/tumblr_nxovj9oY861ur1mffo1_500.gif ", - } }, - {"%mention% cry", new List() - { - "http://i.imgur.com/Xg3i1Qy.gif", - "http://i.imgur.com/3K8DRrU.gif", - "http://i.imgur.com/k58BcAv.gif", - "http://i.imgur.com/I2fLXwo.gif" - } }, - {"%mention% are you real?", new List() - { - "%user%, I will be soon." - } }, - {"%mention% are you there?", new List() - { - "Yes. :)" - } }, - {"%mention% draw", new List() { - "Sorry, I don't gamble, type $draw for that function." - } }, - {"%mention% bb", new List() - { - "Bye %target%" - } }, - {"%mention% call", new List() { - "Calling %target%" - } }, - {"%mention% disguise", new List() { - "https://cdn.discordapp.com/attachments/140007341880901632/156721710458994690/Cc5mixjUYAADgBs.jpg", - "https://cdn.discordapp.com/attachments/140007341880901632/156721715831898113/hqdefault.jpg", - "https://cdn.discordapp.com/attachments/140007341880901632/156721724430352385/okawari_01_haruka_weird_mask.jpg", - "https://cdn.discordapp.com/attachments/140007341880901632/156721728763068417/mustache-best-girl.png" - - } }, - {"%mention% inv", new List() { - "To invite your bot, click on this link -> " - } }, - { "%mention% threaten", new List() { - "You wanna die, %target%?" - } }, - { "%mention% archer", new List() { - "http://i.imgur.com/Bha9NhL.jpg" - } }, - { "%mention% formuoli", new List() { - "http://i.imgur.com/sCHYQhl.jpg" - } }, - { "%mention% mei", new List() { - "http://i.imgur.com/Xkrf5y7.png" - } }, - { "%mention% omega yato", new List() { - "https://cdn.discordapp.com/attachments/168617088892534784/221047921410310144/Yato_Animated.gif" - } }, - { "%mention% smack", new List() { - "%target% https://66.media.tumblr.com/dd5d751f86002fd4a544dcef7a9763d6/tumblr_mjpheaAVj51s725bno1_500.gif", - "%target% https://media.giphy.com/media/jLeyZWgtwgr2U/giphy.gif", - "%target% http://orig11.deviantart.net/2d34/f/2013/339/1/2/golden_time_flower_slap_gif_by_paranoxias-d6wv007.gif", - "%target% http://media.giphy.com/media/LB1kIoSRFTC2Q/giphy.gif", - } } - }; - - public bool DontJoinServers { get; set; } = false; - public bool ForwardMessages { get; set; } = true; - public bool ForwardToAllOwners { get; set; } = false; - public bool IsRotatingStatus { get; set; } = false; - public int BufferSize { get; set; } = 4.MiB(); - - public string[] RaceAnimals { get; internal set; } = { - "🐼", - "🐻", - "🐧", - "🐨", - "🐬", - "🐞", - "🦀", - "🦄" }; - - [JsonIgnore] - public List Quotes { get; set; } = new List(); - - [JsonIgnore] - public List PokemonTypes { get; set; } = new List(); - - public string RemindMessageFormat { get; set; } = "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗"; - - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public Dictionary> CustomReactions { get; set; } - - public List RotatingStatuses { get; set; } = new List(); - public CommandPrefixesModel CommandPrefixes { get; set; } = new CommandPrefixesModel(); - public HashSet ServerBlacklist { get; set; } = new HashSet(); - public HashSet ChannelBlacklist { get; set; } = new HashSet(); - - public HashSet UserBlacklist { get; set; } = new HashSet() { - 105309315895693312, - 119174277298782216, - 143515953525817344 - }; - - [OnDeserialized] - internal void OnDeserialized(StreamingContext context) - { - if (CustomReactions == null) - { - CustomReactions = DefaultCustomReactions; - } - } - [OnSerializing] - internal void OnSerializing(StreamingContext context) - { - if (CustomReactions == null) - { - CustomReactions = DefaultCustomReactions; - } - } - - public string[] _8BallResponses { get; set; } = - { - "Most definitely yes", - "For sure", - "As I see it, yes", - "My sources say yes", - "Yes", - "Most likely", - "Perhaps", - "Maybe", - "Not sure", - "It is uncertain", - "Ask me again later", - "Don't count on it", - "Probably not", - "Very doubtful", - "Most likely no", - "Nope", - "No", - "My sources say no", - "Dont even think about it", - "Definitely no", - "NO - It may cause disease contraction" - }; - - public string CurrencySign { get; set; } = "🌸"; - public string CurrencyName { get; set; } = "NadekoFlower"; - public string DMHelpString { get; set; } = "Type `-h` for help."; - public string HelpString { get; set; } = @"You can use `{0}modules` command to see a list of all modules. -You can use `{0}commands ModuleName` -(for example `{0}commands Administration`) to see a list of all of the commands in that module. -For a specific command help, use `{0}h ""Command name""` (for example `-h ""!m q""`) - - -**LIST OF COMMANDS CAN BE FOUND ON THIS LINK** - - - -Nadeko Support Server: "; - } - - public class CommandPrefixesModel - { - public string Administration { get; set; } = "."; - public string Searches { get; set; } = "~"; - public string NSFW { get; set; } = "~"; - public string Conversations { get; set; } = "<@{0}>"; - public string ClashOfClans { get; set; } = ","; - public string Help { get; set; } = "-"; - public string Music { get; set; } = "!!"; - public string Trello { get; set; } = "trello "; - public string Games { get; set; } = ">"; - public string Gambling { get; set; } = "$"; - public string Permissions { get; set; } = ";"; - public string Programming { get; set; } = "%"; - public string Pokemon { get; set; } = ">"; - public string Utility { get; set; } = "."; - } - - public static class ConfigHandler - { - private static readonly SemaphoreSlim configLock = new SemaphoreSlim(1, 1); - public static async Task SaveConfig() - { - await configLock.WaitAsync(); - try - { - File.WriteAllText("data/config.json", JsonConvert.SerializeObject(NadekoBot.Config, Formatting.Indented)); - } - finally - { - configLock.Release(); - } - } - - public static bool IsBlackListed(MessageEventArgs evArgs) => IsUserBlacklisted(evArgs.User.Id) || - (!evArgs.Channel.IsPrivate && - (IsChannelBlacklisted(evArgs.Channel.Id) || IsServerBlacklisted(evArgs.Server.Id))); - - public static bool IsServerBlacklisted(ulong id) => NadekoBot.Config.ServerBlacklist.Contains(id); - - public static bool IsChannelBlacklisted(ulong id) => NadekoBot.Config.ChannelBlacklist.Contains(id); - - public static bool IsUserBlacklisted(ulong id) => NadekoBot.Config.UserBlacklist.Contains(id); - } - - public class Quote - { - public string Author { get; set; } - public string Text { get; set; } - - public override string ToString() => - $"{Text}\n\t*-{Author}*"; - } - -} diff --git a/NadekoBot/_Models/JSONModels/LocalizedStrings.cs b/NadekoBot/_Models/JSONModels/LocalizedStrings.cs deleted file mode 100644 index 861ebb69..00000000 --- a/NadekoBot/_Models/JSONModels/LocalizedStrings.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.IO; - -namespace NadekoBot.Classes.JSONModels { - public class LocalizedStrings { - public string[] Insults { get; set; } = { - " You are a poop.", " You're a jerk.", - " I will eat you when I get my powers back." - }; - - public string[] Praises { get; set; } = { - " You are cool.", - " You are nice!", - " You did a good job.", - " You did something nice.", - " is awesome!", - " Wow." - }; - - public static string[] GetAvailableLocales() { - Directory.CreateDirectory("data/locales"); - return Directory.GetFiles("data/locales"); - } - - //public static void HandleLocalization() { - // var locales = LocalizedStrings.GetAvailableLocales(); - - - // Console.WriteLine("Pick a language:\n" + - // "1. English"); - // for (var i = 0; i < locales.Length; i++) { - // Console.WriteLine((i + 2) + ". " + Path.GetFileNameWithoutExtension(locales[i])); - // } - // File.WriteAllText("data/locales/english.json", JsonConvert.SerializeObject(new LocalizedStrings(), Formatting.Indented)); - // try { - // Console.WriteLine($"Type in a number from {1} to {locales.Length + 1}\n"); - // var input = Console.ReadLine(); - // if (input != "1") - // Locale = LocalizedStrings.LoadLocale(locales[int.Parse(input) - 2]); - // } catch (Exception ex) { - // Console.ForegroundColor = ConsoleColor.Red; - // Console.WriteLine(ex); - // Console.ReadKey(); - // return; - // } - //} - - public static LocalizedStrings LoadLocale(string localeFile) => - Newtonsoft.Json.JsonConvert.DeserializeObject(File.ReadAllText(localeFile)); - } -} diff --git a/NadekoBot/_Models/JSONModels/MangaResult.cs b/NadekoBot/_Models/JSONModels/MangaResult.cs deleted file mode 100644 index b7cb9f73..00000000 --- a/NadekoBot/_Models/JSONModels/MangaResult.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace NadekoBot.Classes.JSONModels -{ - public class MangaResult - { - public int id; - public string publishing_status; - public string image_url_lge; - public string title_english; - public int total_chapters; - public int total_volumes; - public string description; - - public override string ToString() => - "`Title:` **" + title_english + - "**\n`Status:` " + publishing_status + - "\n`Chapters:` " + total_chapters + - "\n`Volumes:` " + total_volumes + - "\n`Link:` http://anilist.co/manga/" + id + - "\n`Synopsis:` " + description.Substring(0, description.Length > 500 ? 500 : description.Length) + "..." + - "\n`img:` " + image_url_lge; - } -} \ No newline at end of file diff --git a/NadekoBot/_Models/JSONModels/PokemonType.cs b/NadekoBot/_Models/JSONModels/PokemonType.cs deleted file mode 100644 index 02e50e2f..00000000 --- a/NadekoBot/_Models/JSONModels/PokemonType.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Classes.JSONModels -{ - public class PokemonType - { - public PokemonType(string n, string i, string[] m, List multi) - { - Name = n; - Icon = i; - Moves = m; - Multipliers = multi; - } - public string Name { get; set; } - public List Multipliers { get; set; } - public string Icon { get; set; } - public string[] Moves { get; set; } - } - public class PokemonMultiplier - { - public PokemonMultiplier(string t, double m) - { - Type = t; - Multiplication = m; - } - public string Type { get; set; } - public double Multiplication { get; set; } - } -} diff --git a/NadekoBot/_Models/JSONModels/_JSONModels.cs b/NadekoBot/_Models/JSONModels/_JSONModels.cs deleted file mode 100644 index c60d32d8..00000000 --- a/NadekoBot/_Models/JSONModels/_JSONModels.cs +++ /dev/null @@ -1,155 +0,0 @@ -// ReSharper disable InconsistentNaming - -using System.Diagnostics; - -namespace NadekoBot.Classes.JSONModels -{ - public class Credentials - { - public string Token { get; set; } = ""; - public string ClientId { get; set; } = "170254782546575360"; - public ulong BotId { get; set; } = 1231231231231; - public ulong[] OwnerIds { get; set; } = { 123123123123, 5675675679845 }; - public string GoogleAPIKey { get; set; } = ""; - public string SoundCloudClientID { get; set; } = ""; - public string MashapeKey { get; set; } = ""; - public string LOLAPIKey { get; set; } = ""; - public string TrelloAppKey { get; set; } = ""; - public string CarbonKey { get; set; } = ""; - public string OsuAPIKey { get; set; } = ""; - } - [DebuggerDisplay("{items[0].id.playlistId}")] - public class YoutubePlaylistSearch - { - public YtPlaylistItem[] items { get; set; } - } - public class YtPlaylistItem - { - public YtPlaylistId id { get; set; } - } - public class YtPlaylistId - { - public string kind { get; set; } - public string playlistId { get; set; } - } - [DebuggerDisplay("{items[0].id.videoId}")] - public class YoutubeVideoSearch - { - public YtVideoItem[] items { get; set; } - } - public class YtVideoItem - { - public YtVideoId id { get; set; } - } - public class YtVideoId - { - public string kind { get; set; } - public string videoId { get; set; } - } - public class PlaylistItemsSearch - { - public string nextPageToken { get; set; } - public PlaylistItem[] items { get; set; } - } - public class PlaylistItem - { - public YtVideoId contentDetails { get; set; } - } - - #region wikpedia example - // { - // "batchcomplete": true, - // "query": { - // "normalized": [ - // { - // "from": "u3fn92fb32f9yb329f32", - // "to": "U3fn92fb32f9yb329f32" - // } - // ], - // "pages": [ - // { - // "ns": 0, - // "title": "U3fn92fb32f9yb329f32", - // "missing": true, - // "contentmodel": "wikitext", - // "pagelanguage": "en", - // "pagelanguagehtmlcode": "en", - // "pagelanguagedir": "ltr", - // "fullurl": "https://en.wikipedia.org/wiki/U3fn92fb32f9yb329f32", - // "editurl": "https://en.wikipedia.org/w/index.php?title=U3fn92fb32f9yb329f32&action=edit", - // "canonicalurl": "https://en.wikipedia.org/wiki/U3fn92fb32f9yb329f32" - // } - // ] - // } - //} - #endregion - - public class WikipediaApiModel - { - public WikipediaQuery Query { get; set; } - } - - public class WikipediaQuery - { - public WikipediaPage[] Pages { get; set; } - } - - public class WikipediaPage - { - public bool Missing { get; set; } = false; - public string FullUrl { get; set; } - } - - public class WoWJoke - { - public string Question { get; set; } - public string Answer { get; set; } - public override string ToString() => $"`{Question}`\n\n**{Answer}**"; - } -} - -//{ -// "kind": "youtube#searchListResponse", -// "etag": "\"kiOs9cZLH2FUp6r6KJ8eyq_LIOk/hCJTmyH_v57mh_MvnUFSTHfjzBs\"", -// "nextPageToken": "CAEQAA", -// "regionCode": "RS", -// "pageInfo": { -// "totalResults": 4603, -// "resultsPerPage": 1 -// }, -// "items": [ -// { -// "kind": "youtube#searchResult", -// "etag": "\"kiOs9cZLH2FUp6r6KJ8eyq_LIOk/iD1S35mk0xOfwTB_8lpPZ9u-Vzc\"", -// "id": { -// "kind": "youtube#playlist", -// "playlistId": "PLs_KC2CCxJVMfOBnIyW5Kbu_GciNiYNAI" -// }, -// "snippet": { -// "publishedAt": "2016-04-14T11:35:29.000Z", -// "channelId": "UCMLwm18Qa20L2L-HGpgC3jQ", -// "title": "Popular Videos - Otorimonogatari & mousou express", -// "description": "", -// "thumbnails": { -// "default": { -// "url": "https://i.ytimg.com/vi/2FeptLky2mU/default.jpg", -// "width": 120, -// "height": 90 -// }, -// "medium": { -// "url": "https://i.ytimg.com/vi/2FeptLky2mU/mqdefault.jpg", -// "width": 320, -// "height": 180 -// }, -// "high": { -// "url": "https://i.ytimg.com/vi/2FeptLky2mU/hqdefault.jpg", -// "width": 480, -// "height": 360 -// } -// }, -// "channelTitle": "Otorimonogatari - Topic", -// "liveBroadcastContent": "none" -// } -// } -// ] -//} \ No newline at end of file diff --git a/NadekoBot/bin/Debug/Nito.AsyncEx.Enlightenment.dll b/NadekoBot/bin/Debug/Nito.AsyncEx.Enlightenment.dll deleted file mode 100644 index 8af5fe02..00000000 Binary files a/NadekoBot/bin/Debug/Nito.AsyncEx.Enlightenment.dll and /dev/null differ diff --git a/NadekoBot/bin/Debug/Nito.AsyncEx.dll b/NadekoBot/bin/Debug/Nito.AsyncEx.dll deleted file mode 100644 index d39e3fc1..00000000 Binary files a/NadekoBot/bin/Debug/Nito.AsyncEx.dll and /dev/null differ diff --git a/NadekoBot/bin/Debug/WebSocket4Net.dll b/NadekoBot/bin/Debug/WebSocket4Net.dll deleted file mode 100644 index ac6cbce1..00000000 Binary files a/NadekoBot/bin/Debug/WebSocket4Net.dll and /dev/null differ diff --git a/NadekoBot/bin/Debug/credentials_example.json b/NadekoBot/bin/Debug/credentials_example.json deleted file mode 100644 index e5c9f2dd..00000000 --- a/NadekoBot/bin/Debug/credentials_example.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Token": "", - "ClientId": "170254782546575360", - "BotId": 1231231231231, - "OwnerIds": [ - 123123123123, - 5675675679845 - ], - "GoogleAPIKey": "", - "SoundCloudClientID": "", - "MashapeKey": "", - "LOLAPIKey": "", - "TrelloAppKey": "", - "CarbonKey": "", - "OsuAPIKey": "" -} \ No newline at end of file diff --git a/NadekoBot/bin/Debug/data/8ball.json b/NadekoBot/bin/Debug/data/8ball.json deleted file mode 100644 index e69de29b..00000000 diff --git a/NadekoBot/bin/Debug/data/PokemonTypes.json b/NadekoBot/bin/Debug/data/PokemonTypes.json deleted file mode 100644 index 6d7ac74a..00000000 --- a/NadekoBot/bin/Debug/data/PokemonTypes.json +++ /dev/null @@ -1,699 +0,0 @@ -[ - { - "Name": "NORMAL", - "Multipliers": [ - { - "Type": "ROCK", - "Multiplication": 0.5 - }, - { - "Type": "GHOST", - "Multiplication": 0 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - } - ], - "Moves": [ - "sonic boom", - "quick attack", - "doubleslap", - "headbutt" - ], - "Icon": "⭕️" - }, - { - "Name": "FIRE", - "Multipliers": [ - { - "Type": "FIRE", - "Multiplication": 0.5 - }, - { - "Type": "WATER", - "Multiplication": 0.5 - }, - { - "Type": "GRASS", - "Multiplication": 2 - }, - { - "Type": "ICE", - "Multiplication": 2 - }, - { - "Type": "BUG", - "Multiplication": 2 - }, - { - "Type": "ROCK", - "Multiplication": 0.5 - }, - { - "Type": "DRAGON", - "Multiplication": 0.5 - }, - { - "Type": "STEEL", - "Multiplication": 2 - } - ], - "Moves": [ - "incinerate", - "ember", - "fire punch", - "fiery dance" - ], - "Icon": "🔥" - }, - { - "Name": "WATER", - "Multipliers": [ - { - "Type": "FIRE", - "Multiplication": 2 - }, - { - "Type": "WATER", - "Multiplication": 0.5 - }, - { - "Type": "GRASS", - "Multiplication": 0.5 - }, - { - "Type": "GROUND", - "Multiplication": 2 - }, - { - "Type": "ROCK", - "Multiplication": 2 - }, - { - "Type": "DRAGON", - "Multiplication": 0.5 - } - ], - "Moves": [ - "bubblebeam", - "dive", - "whirlpool", - "aqua tail" - ], - "Icon": "💦" - }, - { - "Name": "ELECTRIC", - "Multipliers": [ - { - "Type": "WATER", - "Multiplication": 2 - }, - { - "Type": "ELECTRIC", - "Multiplication": 0.5 - }, - { - "Type": "GRASS", - "Multiplication": 0.5 - }, - { - "Type": "GROUND", - "Multiplication": 0 - }, - { - "Type": "FLYING", - "Multiplication": 2 - }, - { - "Type": "DRAGON", - "Multiplication": 0.5 - } - ], - "Moves": [ - "nuzzle", - "thunderbolt", - "thundershock", - "discharge" - ], - "Icon": "⚡" - }, - { - "Name": "GRASS", - "Multipliers": [ - { - "Type": "FIRE", - "Multiplication": 0.5 - }, - { - "Type": "WATER", - "Multiplication": 2 - }, - { - "Type": "GRASS", - "Multiplication": 0.5 - }, - { - "Type": "ICE", - "Multiplication": 0.5 - }, - { - "Type": "BUG", - "Multiplication": 0.5 - }, - { - "Type": "ROCK", - "Multiplication": 2 - }, - { - "Type": "DRAGON", - "Multiplication": 0.5 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - }, - { - "Type": "POISON", - "Multiplication": 0.5 - }, - { - "Type": "GROUND", - "Multiplication": 2 - } - ], - "Moves": [ - "absorb", - "mega drain", - "vine whip", - "razor leaf" - ], - "Icon": "🍃" - }, - { - "Name": "ICE", - "Multipliers": [ - { - "Type": "FIRE", - "Multiplication": 0.5 - }, - { - "Type": "WATER", - "Multiplication": 0.5 - }, - { - "Type": "GRASS", - "Multiplication": 2 - }, - { - "Type": "ICE", - "Multiplication": 0.5 - }, - { - "Type": "GROUND", - "Multiplication": 2 - }, - { - "Type": "FLYING", - "Multiplication": 2 - }, - { - "Type": "DRAGON", - "Multiplication": 2 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - } - ], - "Moves": [ - "ice ball", - "powder snow", - "avalanche", - "icy wind" - ], - "Icon": "❄" - }, - { - "Name": "FIGHTING", - "Multipliers": [ - { - "Type": "NORMAL", - "Multiplication": 2 - }, - { - "Type": "ICE", - "Multiplication": 2 - }, - { - "Type": "POISON", - "Multiplication": 0.5 - }, - { - "Type": "FLYING", - "Multiplication": 0.5 - }, - { - "Type": "PSYCHIC", - "Multiplication": 0.5 - }, - { - "Type": "BUG", - "Multiplication": 0.5 - }, - { - "Type": "ROCK", - "Multiplication": 2 - }, - { - "Type": "GHOST", - "Multiplication": 0 - }, - { - "Type": "DARK", - "Multiplication": 2 - }, - { - "Type": "STEEL", - "Multiplication": 2 - }, - { - "Type": "FAIRY", - "Multiplication": 0.5 - } - ], - "Moves": [ - "low kick", - "force palm", - "mach punch", - "double kick" - ], - "Icon": "✊" - }, - { - "Name": "POISON", - "Multipliers": [ - { - "Type": "GRASS", - "Multiplication": 2 - }, - { - "Type": "POISON", - "Multiplication": 0.5 - }, - { - "Type": "GROUND", - "Multiplication": 0.5 - }, - { - "Type": "ROCK", - "Multiplication": 0.5 - }, - { - "Type": "GHOST", - "Multiplication": 0.5 - }, - { - "Type": "STEEL", - "Multiplication": 0 - }, - { - "Type": "FAIRY", - "Multiplication": 2 - } - ], - "Moves": [ - "acid", - "smog", - "sludge", - "poison jab" - ], - "Icon": "☠" - }, - { - "Name": "GROUND", - "Multipliers": [ - { - "Type": "FIRE", - "Multiplication": 2 - }, - { - "Type": "ELECTRIC", - "Multiplication": 2 - }, - { - "Type": "GRASS", - "Multiplication": 0.5 - }, - { - "Type": "POISON", - "Multiplication": 2 - }, - { - "Type": "FLYING", - "Multiplication": 0 - }, - { - "Type": "BUG", - "Multiplication": 0.5 - }, - { - "Type": "ROCK", - "Multiplication": 2 - }, - { - "Type": "STEEL", - "Multiplication": 2 - } - ], - "Moves": [ - "mud-slap", - "earthquake", - "bulldoze", - "dig" - ], - "Icon": "🗻" - }, - { - "Name": "FLYING", - "Multipliers": [ - { - "Type": "ELECTRIC", - "Multiplication": 0.5 - }, - { - "Type": "GRASS", - "Multiplication": 2 - }, - { - "Type": "FIGHTING", - "Multiplication": 2 - }, - { - "Type": "BUG", - "Multiplication": 2 - }, - { - "Type": "ROCK", - "Multiplication": 0.5 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - } - ], - "Moves": [ - "peck", - "pluck", - "gust", - "aerial ace" - ], - "Icon": "☁" - }, - { - "Name": "PSYCHIC", - "Multipliers": [ - { - "Type": "FIGHTING", - "Multiplication": 2 - }, - { - "Type": "POISON", - "Multiplication": 2 - }, - { - "Type": "PSYCHIC", - "Multiplication": 0.5 - }, - { - "Type": "DARK", - "Multiplication": 0 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - } - ], - "Moves": [ - "confusion", - "psybeam", - "psywave", - "heart stamp" - ], - "Icon": "🔮" - }, - { - "Name": "BUG", - "Multipliers": [ - { - "Type": "FIRE", - "Multiplication": 0.5 - }, - { - "Type": "GRASS", - "Multiplication": 2 - }, - { - "Type": "FIGHTING", - "Multiplication": 0.5 - }, - { - "Type": "POISON", - "Multiplication": 0.5 - }, - { - "Type": "FLYING", - "Multiplication": 0.5 - }, - { - "Type": "PSYCHIC", - "Multiplication": 2 - }, - { - "Type": "ROCK", - "Multiplication": 0.5 - }, - { - "Type": "DARK", - "Multiplication": 2 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - }, - { - "Type": "FAIRY", - "Multiplication": 0.5 - } - ], - "Moves": [ - "bug bite", - "infestation", - "x-scissors", - "twineedle" - ], - "Icon": "🐛" - }, - { - "Name": "ROCK", - "Multipliers": [ - { - "Type": "FIRE", - "Multiplication": 2 - }, - { - "Type": "ICE", - "Multiplication": 2 - }, - { - "Type": "FIGHTING", - "Multiplication": 0.5 - }, - { - "Type": "GROUND", - "Multiplication": 0.5 - }, - { - "Type": "FLYING", - "Multiplication": 2 - }, - { - "Type": "BUG", - "Multiplication": 2 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - } - ], - "Moves": [ - "rock throw", - "rollout", - "rock tomb", - "rock blast" - ], - "Icon": "💎" - }, - { - "Name": "GHOST", - "Multipliers": [ - { - "Type": "NORMAL", - "Multiplication": 0 - }, - { - "Type": "PSYCHIC", - "Multiplication": 2 - }, - { - "Type": "GHOST", - "Multiplication": 2 - }, - { - "Type": "DARK", - "Multiplication": 0.5 - } - ], - "Moves": [ - "astonish", - "night shade", - "lick", - "ominous wind", - "hex" - ], - "Icon": "👻" - }, - { - "Name": "DRAGON", - "Multipliers": [ - { - "Type": "DRAGON", - "Multiplication": 2 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - }, - { - "Type": "FAIRY", - "Multiplication": 0 - } - ], - "Moves": [ - "dragon tail", - "dragon rage", - "dragonbreath", - "twister" - ], - "Icon": "🐉" - }, - { - "Name": "DARK", - "Multipliers": [ - { - "Type": "FIGHTING", - "Multiplication": 0.5 - }, - { - "Type": "PSYCHIC", - "Multiplication": 2 - }, - { - "Type": "GHOST", - "Multiplication": 2 - }, - { - "Type": "DARK", - "Multiplication": 0.5 - }, - { - "Type": "FAIRY", - "Multiplication": 0.5 - } - ], - "Moves": [ - "pursuit", - "assurance", - "bite", - "faint attack" - ], - "Icon": "✴" - }, - { - "Name": "STEEL", - "Multipliers": [ - { - "Type": "FIRE", - "Multiplication": 0.5 - }, - { - "Type": "WATER", - "Multiplication": 0.5 - }, - { - "Type": "ELECTRIC", - "Multiplication": 0.5 - }, - { - "Type": "ICE", - "Multiplication": 2 - }, - { - "Type": "ROCK", - "Multiplication": 2 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - }, - { - "Type": "FAIRY", - "Multiplication": 2 - } - ], - "Moves": [ - "bullet punch", - "metal burst", - "gear grind", - "magnet bomb" - ], - "Icon": "🔩" - }, - { - "Name": "FAIRY", - "Multipliers": [ - { - "Type": "FIGHTING", - "Multiplication": 2 - }, - { - "Type": "FIRE", - "Multiplication": 0.5 - }, - { - "Type": "DARK", - "Multiplication": 2 - }, - { - "Type": "POISON", - "Multiplication": 0.5 - }, - { - "Type": "STEEL", - "Multiplication": 0.5 - }, - { - "Type": "DRAGON", - "Multiplication": 2 - } - ], - "Moves": [ - "fairy wind", - "draining kiss", - "dazzling gleam", - "play rough" - ], - "Icon": "💫" - } -] diff --git a/NadekoBot/bin/Debug/data/avatar.png b/NadekoBot/bin/Debug/data/avatar.png deleted file mode 100644 index 37862c4b..00000000 Binary files a/NadekoBot/bin/Debug/data/avatar.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/config_example.json b/NadekoBot/bin/Debug/data/config_example.json deleted file mode 100644 index 5c9bb0a4..00000000 --- a/NadekoBot/bin/Debug/data/config_example.json +++ /dev/null @@ -1,162 +0,0 @@ -{ - "DontJoinServers": false, - "ForwardMessages": true, - "ForwardToAllOwners": false, - "IsRotatingStatus": false, - "BufferSize": 4194304, - "RaceAnimals": [ - "🐼", - "🐻", - "🐧", - "🐨", - "🐬", - "🐞", - "🦀", - "🦄" - ], - "RemindMessageFormat": "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗", - "CustomReactions": { - "\\o\\": [ - "/o/" - ], - "/o/": [ - "\\o\\" - ], - "moveto": [ - "(👉 ͡° ͜ʖ ͡°)👉 %target%" - ], - "comeatmebro": [ - "%target% (ง’̀-‘́)ง" - ], - "e": [ - "%user% did it 😒 🔫", - "%target% did it 😒 🔫" - ], - "%mention% insult": [ - "%target% You are a poop.", - "%target% You're a jerk.", - "%target% I will eat you when I get my powers back." - ], - "%mention% praise": [ - "%target% You are cool.", - "%target% You are nice!", - "%target% You did a good job.", - "%target% You did something nice.", - "%target% is awesome!", - "%target% Wow." - ], - "%mention% pat": [ - "http://i.imgur.com/IiQwK12.gif", - "http://i.imgur.com/JCXj8yD.gif", - "http://i.imgur.com/qqBl2bm.gif", - "http://i.imgur.com/eOJlnwP.gif", - "https://45.media.tumblr.com/229ec0458891c4dcd847545c81e760a5/tumblr_mpfy232F4j1rxrpjzo1_r2_500.gif", - "https://media.giphy.com/media/KZQlfylo73AMU/giphy.gif", - "https://media.giphy.com/media/12hvLuZ7uzvCvK/giphy.gif", - "http://gallery1.anivide.com/_full/65030_1382582341.gif", - "https://49.media.tumblr.com/8e8a099c4eba22abd3ec0f70fd087cce/tumblr_nxovj9oY861ur1mffo1_500.gif " - ], - "%mention% cry": [ - "http://i.imgur.com/Xg3i1Qy.gif", - "http://i.imgur.com/3K8DRrU.gif", - "http://i.imgur.com/k58BcAv.gif", - "http://i.imgur.com/I2fLXwo.gif" - ], - "%mention% are you real?": [ - "%user%, I will be soon." - ], - "%mention% are you there?": [ - "Yes. :)" - ], - "%mention% draw": [ - "Sorry, I don't gamble, type $draw for that function." - ], - "%mention% bb": [ - "Bye %target%" - ], - "%mention% call": [ - "Calling %target%" - ], - "%mention% disguise": [ - "https://cdn.discordapp.com/attachments/140007341880901632/156721710458994690/Cc5mixjUYAADgBs.jpg", - "https://cdn.discordapp.com/attachments/140007341880901632/156721715831898113/hqdefault.jpg", - "https://cdn.discordapp.com/attachments/140007341880901632/156721724430352385/okawari_01_haruka_weird_mask.jpg", - "https://cdn.discordapp.com/attachments/140007341880901632/156721728763068417/mustache-best-girl.png" - ], - "%mention% inv": [ - "To invite your bot, click on this link -> " - ], - "%mention% threaten": [ - "You wanna die, %target%?" - ], - "%mention% archer": [ - "http://i.imgur.com/Bha9NhL.jpg" - ], - "%mention% formuoli": [ - "http://i.imgur.com/sCHYQhl.jpg" - ], - "%mention% mei": [ - "http://i.imgur.com/Xkrf5y7.png" - ], - "%mention% omega yato": [ - "https://cdn.discordapp.com/attachments/168617088892534784/221047921410310144/Yato_Animated.gif" - ], - "%mention% smack": [ - "%target% https://66.media.tumblr.com/dd5d751f86002fd4a544dcef7a9763d6/tumblr_mjpheaAVj51s725bno1_500.gif", - "%target% https://media.giphy.com/media/jLeyZWgtwgr2U/giphy.gif", - "%target% http://orig11.deviantart.net/2d34/f/2013/339/1/2/golden_time_flower_slap_gif_by_paranoxias-d6wv007.gif", - "%target% http://media.giphy.com/media/LB1kIoSRFTC2Q/giphy.gif" - ] - }, - "RotatingStatuses": [], - "CommandPrefixes": { - "Administration": ".", - "Searches": "~", - "NSFW": "~", - "Conversations": "<@{0}>", - "ClashOfClans": ",", - "Help": "-", - "Music": "!!", - "Trello": "trello ", - "Games": ">", - "Gambling": "$", - "Permissions": ";", - "Programming": "%", - "Pokemon": ">", - "Utility": "." - }, - "ServerBlacklist": [], - "ChannelBlacklist": [], - "UserBlacklist": [ - 105309315895693312, - 119174277298782216, - 143515953525817344 - ], - "_8BallResponses": [ - "Most definitely yes", - "For sure", - "As I see it, yes", - "My sources say yes", - "Yes", - "Most likely", - "Perhaps", - "Maybe", - "Not sure", - "It is uncertain", - "Ask me again later", - "Don't count on it", - "Probably not", - "Very doubtful", - "Most likely no", - "Nope", - "No", - "My sources say no", - "Dont even think about it", - "Definitely no", - "NO - It may cause disease contraction" - ], - "CurrencySign": "🌸", - "CurrencyName": "NadekoFlower", - "DMHelpString": "Type `-h` for help.", - "HelpString": "You can use `{0}modules` command to see a list of all modules.\r\nYou can use `{0}commands ModuleName`\r\n(for example `{0}commands Administration`) to see a list of all of the commands in that module.\r\nFor a specific command help, use `{0}h \"Command name\"` (for example `-h \"!m q\"`)\r\n\r\n\r\n**LIST OF COMMANDS CAN BE FOUND ON THIS LINK**\r\n\r\n\r\n\r\nNadeko Support Server: " -} \ No newline at end of file diff --git a/NadekoBot/bin/Debug/data/lol/_ERROR.png b/NadekoBot/bin/Debug/data/lol/_ERROR.png deleted file mode 100644 index 81da2fd7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/_ERROR.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/bg.png b/NadekoBot/bin/Debug/data/lol/bg.png deleted file mode 100644 index 97fcf55e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/bg.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Aatrox.png b/NadekoBot/bin/Debug/data/lol/champions/Aatrox.png deleted file mode 100644 index cc134d6c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Aatrox.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ahri.png b/NadekoBot/bin/Debug/data/lol/champions/Ahri.png deleted file mode 100644 index 73b0a5b7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Ahri.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Akali.png b/NadekoBot/bin/Debug/data/lol/champions/Akali.png deleted file mode 100644 index b2d33c75..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Akali.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Alistar.png b/NadekoBot/bin/Debug/data/lol/champions/Alistar.png deleted file mode 100644 index da8f8c14..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Alistar.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Amumu.png b/NadekoBot/bin/Debug/data/lol/champions/Amumu.png deleted file mode 100644 index 6909f9d1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Amumu.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Anivia.png b/NadekoBot/bin/Debug/data/lol/champions/Anivia.png deleted file mode 100644 index 8296931d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Anivia.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Annie.png b/NadekoBot/bin/Debug/data/lol/champions/Annie.png deleted file mode 100644 index ea230cdb..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Annie.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ashe.png b/NadekoBot/bin/Debug/data/lol/champions/Ashe.png deleted file mode 100644 index 85cd8db7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Ashe.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/AurelionSol.png b/NadekoBot/bin/Debug/data/lol/champions/AurelionSol.png deleted file mode 100644 index 25ed0c66..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/AurelionSol.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Azir.png b/NadekoBot/bin/Debug/data/lol/champions/Azir.png deleted file mode 100644 index c0a76cb5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Azir.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Bard.png b/NadekoBot/bin/Debug/data/lol/champions/Bard.png deleted file mode 100644 index 875e0467..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Bard.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Blitzcrank.png b/NadekoBot/bin/Debug/data/lol/champions/Blitzcrank.png deleted file mode 100644 index c633d637..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Blitzcrank.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Brand.png b/NadekoBot/bin/Debug/data/lol/champions/Brand.png deleted file mode 100644 index 4b91b2da..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Brand.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Braum.png b/NadekoBot/bin/Debug/data/lol/champions/Braum.png deleted file mode 100644 index 4fc61570..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Braum.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Caitlyn.png b/NadekoBot/bin/Debug/data/lol/champions/Caitlyn.png deleted file mode 100644 index c60efd9b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Caitlyn.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Cassiopeia.png b/NadekoBot/bin/Debug/data/lol/champions/Cassiopeia.png deleted file mode 100644 index ac76b516..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Cassiopeia.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/ChoGath.png b/NadekoBot/bin/Debug/data/lol/champions/ChoGath.png deleted file mode 100644 index 8a18e92e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/ChoGath.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Corki.png b/NadekoBot/bin/Debug/data/lol/champions/Corki.png deleted file mode 100644 index d1d1a709..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Corki.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Darius.png b/NadekoBot/bin/Debug/data/lol/champions/Darius.png deleted file mode 100644 index cb11b9ea..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Darius.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Diana.png b/NadekoBot/bin/Debug/data/lol/champions/Diana.png deleted file mode 100644 index d9ae05ff..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Diana.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/DrMundo.png b/NadekoBot/bin/Debug/data/lol/champions/DrMundo.png deleted file mode 100644 index dd00d3ff..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/DrMundo.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Draven.png b/NadekoBot/bin/Debug/data/lol/champions/Draven.png deleted file mode 100644 index 838eb1cc..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Draven.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ekko.png b/NadekoBot/bin/Debug/data/lol/champions/Ekko.png deleted file mode 100644 index c0d5365b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Ekko.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Elise.png b/NadekoBot/bin/Debug/data/lol/champions/Elise.png deleted file mode 100644 index eb192d62..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Elise.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Evelynn.png b/NadekoBot/bin/Debug/data/lol/champions/Evelynn.png deleted file mode 100644 index 29d10481..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Evelynn.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ezreal.png b/NadekoBot/bin/Debug/data/lol/champions/Ezreal.png deleted file mode 100644 index 4a5255eb..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Ezreal.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Fiddlesticks.png b/NadekoBot/bin/Debug/data/lol/champions/Fiddlesticks.png deleted file mode 100644 index 91c8aee8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Fiddlesticks.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Fiora.png b/NadekoBot/bin/Debug/data/lol/champions/Fiora.png deleted file mode 100644 index c4069177..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Fiora.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Fizz.png b/NadekoBot/bin/Debug/data/lol/champions/Fizz.png deleted file mode 100644 index e5c3f156..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Fizz.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Galio.png b/NadekoBot/bin/Debug/data/lol/champions/Galio.png deleted file mode 100644 index 57fa614a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Galio.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Gangplank.png b/NadekoBot/bin/Debug/data/lol/champions/Gangplank.png deleted file mode 100644 index 7cb75cd8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Gangplank.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Garen.png b/NadekoBot/bin/Debug/data/lol/champions/Garen.png deleted file mode 100644 index 6bb51b36..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Garen.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Gnar.png b/NadekoBot/bin/Debug/data/lol/champions/Gnar.png deleted file mode 100644 index d42735bf..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Gnar.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Gragas.png b/NadekoBot/bin/Debug/data/lol/champions/Gragas.png deleted file mode 100644 index 71ae4f32..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Gragas.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Graves.png b/NadekoBot/bin/Debug/data/lol/champions/Graves.png deleted file mode 100644 index 7633c3be..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Graves.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Hecarim.png b/NadekoBot/bin/Debug/data/lol/champions/Hecarim.png deleted file mode 100644 index bccf1e32..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Hecarim.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Heimerdinger.png b/NadekoBot/bin/Debug/data/lol/champions/Heimerdinger.png deleted file mode 100644 index 41842f2b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Heimerdinger.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Illaoi.png b/NadekoBot/bin/Debug/data/lol/champions/Illaoi.png deleted file mode 100644 index e6c13c6b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Illaoi.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Irelia.png b/NadekoBot/bin/Debug/data/lol/champions/Irelia.png deleted file mode 100644 index ea4204a8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Irelia.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Janna.png b/NadekoBot/bin/Debug/data/lol/champions/Janna.png deleted file mode 100644 index 6c302f8c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Janna.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/JarvanIV.png b/NadekoBot/bin/Debug/data/lol/champions/JarvanIV.png deleted file mode 100644 index b61f05a7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/JarvanIV.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Jax.png b/NadekoBot/bin/Debug/data/lol/champions/Jax.png deleted file mode 100644 index 8c8eb368..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Jax.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Jayce.png b/NadekoBot/bin/Debug/data/lol/champions/Jayce.png deleted file mode 100644 index ae4bd70d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Jayce.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Jhin.png b/NadekoBot/bin/Debug/data/lol/champions/Jhin.png deleted file mode 100644 index 6af4d337..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Jhin.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Jinx.png b/NadekoBot/bin/Debug/data/lol/champions/Jinx.png deleted file mode 100644 index c3c003c1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Jinx.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kalista.png b/NadekoBot/bin/Debug/data/lol/champions/Kalista.png deleted file mode 100644 index b8e4f9b4..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Kalista.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Karma.png b/NadekoBot/bin/Debug/data/lol/champions/Karma.png deleted file mode 100644 index f36a424d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Karma.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Karthus.png b/NadekoBot/bin/Debug/data/lol/champions/Karthus.png deleted file mode 100644 index e53ca503..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Karthus.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kassadin.png b/NadekoBot/bin/Debug/data/lol/champions/Kassadin.png deleted file mode 100644 index 54b98267..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Kassadin.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Katarina.png b/NadekoBot/bin/Debug/data/lol/champions/Katarina.png deleted file mode 100644 index d77ae601..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Katarina.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kayle.png b/NadekoBot/bin/Debug/data/lol/champions/Kayle.png deleted file mode 100644 index 053f470d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Kayle.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kennen.png b/NadekoBot/bin/Debug/data/lol/champions/Kennen.png deleted file mode 100644 index e3d8aef8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Kennen.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/KhaZix.png b/NadekoBot/bin/Debug/data/lol/champions/KhaZix.png deleted file mode 100644 index fd5aad52..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/KhaZix.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kindred.png b/NadekoBot/bin/Debug/data/lol/champions/Kindred.png deleted file mode 100644 index 438a59d5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Kindred.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/KogMaw.png b/NadekoBot/bin/Debug/data/lol/champions/KogMaw.png deleted file mode 100644 index 78423792..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/KogMaw.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/LeBlanc.png b/NadekoBot/bin/Debug/data/lol/champions/LeBlanc.png deleted file mode 100644 index db527d66..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/LeBlanc.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/LeeSin.png b/NadekoBot/bin/Debug/data/lol/champions/LeeSin.png deleted file mode 100644 index 2d820b5a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/LeeSin.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Leona.png b/NadekoBot/bin/Debug/data/lol/champions/Leona.png deleted file mode 100644 index f73622ee..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Leona.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Lissandra.png b/NadekoBot/bin/Debug/data/lol/champions/Lissandra.png deleted file mode 100644 index 88405d8f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Lissandra.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Lucian.png b/NadekoBot/bin/Debug/data/lol/champions/Lucian.png deleted file mode 100644 index 8d47328c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Lucian.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Lulu.png b/NadekoBot/bin/Debug/data/lol/champions/Lulu.png deleted file mode 100644 index b98d5446..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Lulu.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Lux.png b/NadekoBot/bin/Debug/data/lol/champions/Lux.png deleted file mode 100644 index 90b9011b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Lux.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Malphite.png b/NadekoBot/bin/Debug/data/lol/champions/Malphite.png deleted file mode 100644 index db4148a5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Malphite.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Malzahar.png b/NadekoBot/bin/Debug/data/lol/champions/Malzahar.png deleted file mode 100644 index 0da5f152..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Malzahar.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Maokai.png b/NadekoBot/bin/Debug/data/lol/champions/Maokai.png deleted file mode 100644 index 10c208e9..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Maokai.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/MasterYi.png b/NadekoBot/bin/Debug/data/lol/champions/MasterYi.png deleted file mode 100644 index 39c34939..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/MasterYi.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/MissFortune.png b/NadekoBot/bin/Debug/data/lol/champions/MissFortune.png deleted file mode 100644 index 5a73d09f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/MissFortune.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/MonkeyKing.png b/NadekoBot/bin/Debug/data/lol/champions/MonkeyKing.png deleted file mode 100644 index 2415c0e0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/MonkeyKing.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Mordekaiser.png b/NadekoBot/bin/Debug/data/lol/champions/Mordekaiser.png deleted file mode 100644 index 3e888ca5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Mordekaiser.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Morgana.png b/NadekoBot/bin/Debug/data/lol/champions/Morgana.png deleted file mode 100644 index d4c173cb..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Morgana.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nami.png b/NadekoBot/bin/Debug/data/lol/champions/Nami.png deleted file mode 100644 index 3dbcb332..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Nami.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nasus.png b/NadekoBot/bin/Debug/data/lol/champions/Nasus.png deleted file mode 100644 index f598724d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Nasus.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nautilus.png b/NadekoBot/bin/Debug/data/lol/champions/Nautilus.png deleted file mode 100644 index 00757c50..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Nautilus.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nidalee.png b/NadekoBot/bin/Debug/data/lol/champions/Nidalee.png deleted file mode 100644 index 3db30fc0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Nidalee.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nocturne.png b/NadekoBot/bin/Debug/data/lol/champions/Nocturne.png deleted file mode 100644 index ad922792..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Nocturne.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nunu.png b/NadekoBot/bin/Debug/data/lol/champions/Nunu.png deleted file mode 100644 index 08619c32..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Nunu.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Olaf.png b/NadekoBot/bin/Debug/data/lol/champions/Olaf.png deleted file mode 100644 index 4554fb43..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Olaf.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Orianna.png b/NadekoBot/bin/Debug/data/lol/champions/Orianna.png deleted file mode 100644 index d6307350..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Orianna.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Pantheon.png b/NadekoBot/bin/Debug/data/lol/champions/Pantheon.png deleted file mode 100644 index bd571bbb..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Pantheon.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Poppy.png b/NadekoBot/bin/Debug/data/lol/champions/Poppy.png deleted file mode 100644 index 807243ff..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Poppy.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Quinn.png b/NadekoBot/bin/Debug/data/lol/champions/Quinn.png deleted file mode 100644 index 7d368dfe..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Quinn.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Rammus.png b/NadekoBot/bin/Debug/data/lol/champions/Rammus.png deleted file mode 100644 index d08befdd..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Rammus.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/RekSai.png b/NadekoBot/bin/Debug/data/lol/champions/RekSai.png deleted file mode 100644 index 370e5c35..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/RekSai.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Renekton.png b/NadekoBot/bin/Debug/data/lol/champions/Renekton.png deleted file mode 100644 index 658e30a5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Renekton.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Rengar.png b/NadekoBot/bin/Debug/data/lol/champions/Rengar.png deleted file mode 100644 index fa4ba35a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Rengar.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Riven.png b/NadekoBot/bin/Debug/data/lol/champions/Riven.png deleted file mode 100644 index e8a23d6d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Riven.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Rumble.png b/NadekoBot/bin/Debug/data/lol/champions/Rumble.png deleted file mode 100644 index b1615d69..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Rumble.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ryze.png b/NadekoBot/bin/Debug/data/lol/champions/Ryze.png deleted file mode 100644 index 928c960a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Ryze.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Sejuani.png b/NadekoBot/bin/Debug/data/lol/champions/Sejuani.png deleted file mode 100644 index d52b6a32..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Sejuani.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Shaco.png b/NadekoBot/bin/Debug/data/lol/champions/Shaco.png deleted file mode 100644 index 4648fe76..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Shaco.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Shen.png b/NadekoBot/bin/Debug/data/lol/champions/Shen.png deleted file mode 100644 index 6763a113..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Shen.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Shyvana.png b/NadekoBot/bin/Debug/data/lol/champions/Shyvana.png deleted file mode 100644 index fd166337..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Shyvana.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Singed.png b/NadekoBot/bin/Debug/data/lol/champions/Singed.png deleted file mode 100644 index 3685b16c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Singed.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Sion.png b/NadekoBot/bin/Debug/data/lol/champions/Sion.png deleted file mode 100644 index ad523edc..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Sion.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Sivir.png b/NadekoBot/bin/Debug/data/lol/champions/Sivir.png deleted file mode 100644 index 931a43e7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Sivir.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Skarner.png b/NadekoBot/bin/Debug/data/lol/champions/Skarner.png deleted file mode 100644 index 076b087e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Skarner.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Sona.png b/NadekoBot/bin/Debug/data/lol/champions/Sona.png deleted file mode 100644 index badba7c1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Sona.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Soraka.png b/NadekoBot/bin/Debug/data/lol/champions/Soraka.png deleted file mode 100644 index 30674654..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Soraka.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Swain.png b/NadekoBot/bin/Debug/data/lol/champions/Swain.png deleted file mode 100644 index 6bec70da..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Swain.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Syndra.png b/NadekoBot/bin/Debug/data/lol/champions/Syndra.png deleted file mode 100644 index 848acfab..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Syndra.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/TahmKench.png b/NadekoBot/bin/Debug/data/lol/champions/TahmKench.png deleted file mode 100644 index a1950433..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/TahmKench.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Talon.png b/NadekoBot/bin/Debug/data/lol/champions/Talon.png deleted file mode 100644 index 7b9393a8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Talon.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Taric.png b/NadekoBot/bin/Debug/data/lol/champions/Taric.png deleted file mode 100644 index ff40176b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Taric.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Teemo.png b/NadekoBot/bin/Debug/data/lol/champions/Teemo.png deleted file mode 100644 index 5a411830..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Teemo.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Thresh.png b/NadekoBot/bin/Debug/data/lol/champions/Thresh.png deleted file mode 100644 index aae76522..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Thresh.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Thumbs.db b/NadekoBot/bin/Debug/data/lol/champions/Thumbs.db deleted file mode 100644 index 828509b3..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Thumbs.db and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Tristana.png b/NadekoBot/bin/Debug/data/lol/champions/Tristana.png deleted file mode 100644 index 322bfeb0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Tristana.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Trundle.png b/NadekoBot/bin/Debug/data/lol/champions/Trundle.png deleted file mode 100644 index 31997cb3..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Trundle.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Tryndamere.png b/NadekoBot/bin/Debug/data/lol/champions/Tryndamere.png deleted file mode 100644 index 4d158300..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Tryndamere.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/TwistedFate.png b/NadekoBot/bin/Debug/data/lol/champions/TwistedFate.png deleted file mode 100644 index c32c450d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/TwistedFate.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Twitch.png b/NadekoBot/bin/Debug/data/lol/champions/Twitch.png deleted file mode 100644 index 844a3a19..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Twitch.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Udyr.png b/NadekoBot/bin/Debug/data/lol/champions/Udyr.png deleted file mode 100644 index dd8644e8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Udyr.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Urgot.png b/NadekoBot/bin/Debug/data/lol/champions/Urgot.png deleted file mode 100644 index 65d1e7c3..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Urgot.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Varus.png b/NadekoBot/bin/Debug/data/lol/champions/Varus.png deleted file mode 100644 index 9c1ee63b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Varus.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Vayne.png b/NadekoBot/bin/Debug/data/lol/champions/Vayne.png deleted file mode 100644 index c6f0e200..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Vayne.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Veigar.png b/NadekoBot/bin/Debug/data/lol/champions/Veigar.png deleted file mode 100644 index f6b2e281..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Veigar.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Velkoz.png b/NadekoBot/bin/Debug/data/lol/champions/Velkoz.png deleted file mode 100644 index e622c834..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Velkoz.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Vi.png b/NadekoBot/bin/Debug/data/lol/champions/Vi.png deleted file mode 100644 index 4a5c87f5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Vi.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Viktor.png b/NadekoBot/bin/Debug/data/lol/champions/Viktor.png deleted file mode 100644 index 80a56bef..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Viktor.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Vladimir.png b/NadekoBot/bin/Debug/data/lol/champions/Vladimir.png deleted file mode 100644 index ccc5070d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Vladimir.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Volibear.png b/NadekoBot/bin/Debug/data/lol/champions/Volibear.png deleted file mode 100644 index d7abdcac..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Volibear.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Warwick.png b/NadekoBot/bin/Debug/data/lol/champions/Warwick.png deleted file mode 100644 index 66ba2337..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Warwick.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Xerath.png b/NadekoBot/bin/Debug/data/lol/champions/Xerath.png deleted file mode 100644 index ab8524db..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Xerath.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/XinZhao.png b/NadekoBot/bin/Debug/data/lol/champions/XinZhao.png deleted file mode 100644 index 01714749..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/XinZhao.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Yasuo.png b/NadekoBot/bin/Debug/data/lol/champions/Yasuo.png deleted file mode 100644 index 5a82c79f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Yasuo.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Yorick.png b/NadekoBot/bin/Debug/data/lol/champions/Yorick.png deleted file mode 100644 index e805c118..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Yorick.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Zac.png b/NadekoBot/bin/Debug/data/lol/champions/Zac.png deleted file mode 100644 index 39d7d3bf..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Zac.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Zed.png b/NadekoBot/bin/Debug/data/lol/champions/Zed.png deleted file mode 100644 index 1da89135..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Zed.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ziggs.png b/NadekoBot/bin/Debug/data/lol/champions/Ziggs.png deleted file mode 100644 index fcc9c6f0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Ziggs.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Zilean.png b/NadekoBot/bin/Debug/data/lol/champions/Zilean.png deleted file mode 100644 index fc37b79a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Zilean.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/champions/Zyra.png b/NadekoBot/bin/Debug/data/lol/champions/Zyra.png deleted file mode 100644 index b4281fdc..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Zyra.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/ex1.png b/NadekoBot/bin/Debug/data/lol/ex1.png deleted file mode 100644 index 0c9958a5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/ex1.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/ex2.png b/NadekoBot/bin/Debug/data/lol/ex2.png deleted file mode 100644 index 5ce92c47..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/ex2.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1001.png b/NadekoBot/bin/Debug/data/lol/items/1001.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1001.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1004.png b/NadekoBot/bin/Debug/data/lol/items/1004.png deleted file mode 100644 index ecc2b1e4..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1004.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1006.png b/NadekoBot/bin/Debug/data/lol/items/1006.png deleted file mode 100644 index 5f2b48a5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1006.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1011.png b/NadekoBot/bin/Debug/data/lol/items/1011.png deleted file mode 100644 index d4665db5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1011.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1018.png b/NadekoBot/bin/Debug/data/lol/items/1018.png deleted file mode 100644 index 2257a572..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1018.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1026.png b/NadekoBot/bin/Debug/data/lol/items/1026.png deleted file mode 100644 index 1a8b63e5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1026.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1027.png b/NadekoBot/bin/Debug/data/lol/items/1027.png deleted file mode 100644 index 3cc59c8e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1027.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1028.png b/NadekoBot/bin/Debug/data/lol/items/1028.png deleted file mode 100644 index 6e53ee2a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1028.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1029.png b/NadekoBot/bin/Debug/data/lol/items/1029.png deleted file mode 100644 index ff4a9e49..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1029.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1031.png b/NadekoBot/bin/Debug/data/lol/items/1031.png deleted file mode 100644 index 8f12ac0f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1031.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1033.png b/NadekoBot/bin/Debug/data/lol/items/1033.png deleted file mode 100644 index 903bc505..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1033.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1036.png b/NadekoBot/bin/Debug/data/lol/items/1036.png deleted file mode 100644 index af5c63e7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1036.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1037.png b/NadekoBot/bin/Debug/data/lol/items/1037.png deleted file mode 100644 index d534de94..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1037.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1038.png b/NadekoBot/bin/Debug/data/lol/items/1038.png deleted file mode 100644 index 9e4deece..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1038.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1039.png b/NadekoBot/bin/Debug/data/lol/items/1039.png deleted file mode 100644 index 7a090382..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1039.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1041.png b/NadekoBot/bin/Debug/data/lol/items/1041.png deleted file mode 100644 index 0792db39..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1041.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1042.png b/NadekoBot/bin/Debug/data/lol/items/1042.png deleted file mode 100644 index d9e1bb23..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1042.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1043.png b/NadekoBot/bin/Debug/data/lol/items/1043.png deleted file mode 100644 index 96dc904b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1043.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1051.png b/NadekoBot/bin/Debug/data/lol/items/1051.png deleted file mode 100644 index b26db5fa..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1051.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1052.png b/NadekoBot/bin/Debug/data/lol/items/1052.png deleted file mode 100644 index ee83467a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1052.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1053.png b/NadekoBot/bin/Debug/data/lol/items/1053.png deleted file mode 100644 index 15e6c122..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1053.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1054.png b/NadekoBot/bin/Debug/data/lol/items/1054.png deleted file mode 100644 index 8020d696..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1054.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1055.png b/NadekoBot/bin/Debug/data/lol/items/1055.png deleted file mode 100644 index a8601f0b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1055.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1056.png b/NadekoBot/bin/Debug/data/lol/items/1056.png deleted file mode 100644 index ff025a43..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1056.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1057.png b/NadekoBot/bin/Debug/data/lol/items/1057.png deleted file mode 100644 index e6306879..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1057.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1058.png b/NadekoBot/bin/Debug/data/lol/items/1058.png deleted file mode 100644 index 7455d597..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1058.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1062.png b/NadekoBot/bin/Debug/data/lol/items/1062.png deleted file mode 100644 index e640fee7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1062.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1063.png b/NadekoBot/bin/Debug/data/lol/items/1063.png deleted file mode 100644 index d86e6fac..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1063.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1082.png b/NadekoBot/bin/Debug/data/lol/items/1082.png deleted file mode 100644 index b8e5e1e0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1082.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1083.png b/NadekoBot/bin/Debug/data/lol/items/1083.png deleted file mode 100644 index 79d06a7f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1083.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1300.png b/NadekoBot/bin/Debug/data/lol/items/1300.png deleted file mode 100644 index fe44d630..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1300.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1301.png b/NadekoBot/bin/Debug/data/lol/items/1301.png deleted file mode 100644 index 82af9402..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1301.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1302.png b/NadekoBot/bin/Debug/data/lol/items/1302.png deleted file mode 100644 index 0fbf15d6..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1302.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1303.png b/NadekoBot/bin/Debug/data/lol/items/1303.png deleted file mode 100644 index 9bffe41b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1303.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1305.png b/NadekoBot/bin/Debug/data/lol/items/1305.png deleted file mode 100644 index e290daac..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1305.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1306.png b/NadekoBot/bin/Debug/data/lol/items/1306.png deleted file mode 100644 index 58662ecd..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1306.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1307.png b/NadekoBot/bin/Debug/data/lol/items/1307.png deleted file mode 100644 index 768b6389..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1307.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1308.png b/NadekoBot/bin/Debug/data/lol/items/1308.png deleted file mode 100644 index d4a59697..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1308.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1310.png b/NadekoBot/bin/Debug/data/lol/items/1310.png deleted file mode 100644 index 1a406e59..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1310.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1311.png b/NadekoBot/bin/Debug/data/lol/items/1311.png deleted file mode 100644 index c02d337f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1311.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1312.png b/NadekoBot/bin/Debug/data/lol/items/1312.png deleted file mode 100644 index f145fe9f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1312.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1313.png b/NadekoBot/bin/Debug/data/lol/items/1313.png deleted file mode 100644 index 0ccf6708..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1313.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1315.png b/NadekoBot/bin/Debug/data/lol/items/1315.png deleted file mode 100644 index 1d38567e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1315.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1316.png b/NadekoBot/bin/Debug/data/lol/items/1316.png deleted file mode 100644 index 51bb5b2d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1316.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1317.png b/NadekoBot/bin/Debug/data/lol/items/1317.png deleted file mode 100644 index 543afb49..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1317.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1318.png b/NadekoBot/bin/Debug/data/lol/items/1318.png deleted file mode 100644 index 8d0d25d9..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1318.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1320.png b/NadekoBot/bin/Debug/data/lol/items/1320.png deleted file mode 100644 index 6604c0b8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1320.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1321.png b/NadekoBot/bin/Debug/data/lol/items/1321.png deleted file mode 100644 index 9ae315c0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1321.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1322.png b/NadekoBot/bin/Debug/data/lol/items/1322.png deleted file mode 100644 index 5f8d0c45..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1322.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1323.png b/NadekoBot/bin/Debug/data/lol/items/1323.png deleted file mode 100644 index 5bf08138..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1323.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1325.png b/NadekoBot/bin/Debug/data/lol/items/1325.png deleted file mode 100644 index 7aa8d2f6..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1325.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1326.png b/NadekoBot/bin/Debug/data/lol/items/1326.png deleted file mode 100644 index 3c7af384..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1326.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1327.png b/NadekoBot/bin/Debug/data/lol/items/1327.png deleted file mode 100644 index bc19caf3..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1327.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1328.png b/NadekoBot/bin/Debug/data/lol/items/1328.png deleted file mode 100644 index 1c7aeb29..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1328.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1330.png b/NadekoBot/bin/Debug/data/lol/items/1330.png deleted file mode 100644 index 3ef27eab..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1330.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1331.png b/NadekoBot/bin/Debug/data/lol/items/1331.png deleted file mode 100644 index 4e7bc0d9..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1331.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1332.png b/NadekoBot/bin/Debug/data/lol/items/1332.png deleted file mode 100644 index b7131442..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1332.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1333.png b/NadekoBot/bin/Debug/data/lol/items/1333.png deleted file mode 100644 index dde46447..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1333.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1400.png b/NadekoBot/bin/Debug/data/lol/items/1400.png deleted file mode 100644 index 2360f39a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1400.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1401.png b/NadekoBot/bin/Debug/data/lol/items/1401.png deleted file mode 100644 index e5f5bd5c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1401.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1402.png b/NadekoBot/bin/Debug/data/lol/items/1402.png deleted file mode 100644 index 099ee1eb..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1402.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1403.png b/NadekoBot/bin/Debug/data/lol/items/1403.png deleted file mode 100644 index 4005b0e1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1403.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1404.png b/NadekoBot/bin/Debug/data/lol/items/1404.png deleted file mode 100644 index a45f9481..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1404.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1405.png b/NadekoBot/bin/Debug/data/lol/items/1405.png deleted file mode 100644 index a68b37c4..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1405.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1406.png b/NadekoBot/bin/Debug/data/lol/items/1406.png deleted file mode 100644 index d2d0312f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1406.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1407.png b/NadekoBot/bin/Debug/data/lol/items/1407.png deleted file mode 100644 index 73d4e506..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1407.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1408.png b/NadekoBot/bin/Debug/data/lol/items/1408.png deleted file mode 100644 index 95b5c70f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1408.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1409.png b/NadekoBot/bin/Debug/data/lol/items/1409.png deleted file mode 100644 index 7d48ab57..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1409.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1410.png b/NadekoBot/bin/Debug/data/lol/items/1410.png deleted file mode 100644 index b182c6c8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1410.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1411.png b/NadekoBot/bin/Debug/data/lol/items/1411.png deleted file mode 100644 index 1358bc82..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1411.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1412.png b/NadekoBot/bin/Debug/data/lol/items/1412.png deleted file mode 100644 index 16436e72..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1412.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1413.png b/NadekoBot/bin/Debug/data/lol/items/1413.png deleted file mode 100644 index 6043e16d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1413.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1414.png b/NadekoBot/bin/Debug/data/lol/items/1414.png deleted file mode 100644 index 7a4bf7b0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1414.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/1415.png b/NadekoBot/bin/Debug/data/lol/items/1415.png deleted file mode 100644 index 4532c4c5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/1415.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2003.png b/NadekoBot/bin/Debug/data/lol/items/2003.png deleted file mode 100644 index 5c9865dc..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2003.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2009.png b/NadekoBot/bin/Debug/data/lol/items/2009.png deleted file mode 100644 index a29abb02..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2009.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2010.png b/NadekoBot/bin/Debug/data/lol/items/2010.png deleted file mode 100644 index a29abb02..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2010.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2015.png b/NadekoBot/bin/Debug/data/lol/items/2015.png deleted file mode 100644 index 681a35c5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2015.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2031.png b/NadekoBot/bin/Debug/data/lol/items/2031.png deleted file mode 100644 index c1ec6fec..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2031.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2032.png b/NadekoBot/bin/Debug/data/lol/items/2032.png deleted file mode 100644 index dd93f4d7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2032.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2033.png b/NadekoBot/bin/Debug/data/lol/items/2033.png deleted file mode 100644 index a101d9f5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2033.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2043.png b/NadekoBot/bin/Debug/data/lol/items/2043.png deleted file mode 100644 index ebc17012..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2043.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2045.png b/NadekoBot/bin/Debug/data/lol/items/2045.png deleted file mode 100644 index 2de6e71f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2045.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2047.png b/NadekoBot/bin/Debug/data/lol/items/2047.png deleted file mode 100644 index c413b6a1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2047.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2049.png b/NadekoBot/bin/Debug/data/lol/items/2049.png deleted file mode 100644 index 4b7a3c53..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2049.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2050.png b/NadekoBot/bin/Debug/data/lol/items/2050.png deleted file mode 100644 index 264134ee..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2050.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2051.png b/NadekoBot/bin/Debug/data/lol/items/2051.png deleted file mode 100644 index e175bf06..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2051.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2052.png b/NadekoBot/bin/Debug/data/lol/items/2052.png deleted file mode 100644 index 81de6934..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2052.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2053.png b/NadekoBot/bin/Debug/data/lol/items/2053.png deleted file mode 100644 index 65afb2f7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2053.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2054.png b/NadekoBot/bin/Debug/data/lol/items/2054.png deleted file mode 100644 index 81de6934..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2054.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2138.png b/NadekoBot/bin/Debug/data/lol/items/2138.png deleted file mode 100644 index 6bd8ebdc..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2138.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2139.png b/NadekoBot/bin/Debug/data/lol/items/2139.png deleted file mode 100644 index c99e69d4..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2139.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2140.png b/NadekoBot/bin/Debug/data/lol/items/2140.png deleted file mode 100644 index 108f2211..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2140.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2301.png b/NadekoBot/bin/Debug/data/lol/items/2301.png deleted file mode 100644 index 30dd3404..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2301.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2302.png b/NadekoBot/bin/Debug/data/lol/items/2302.png deleted file mode 100644 index c08a7345..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2302.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/2303.png b/NadekoBot/bin/Debug/data/lol/items/2303.png deleted file mode 100644 index 1e3232a8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/2303.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3001.png b/NadekoBot/bin/Debug/data/lol/items/3001.png deleted file mode 100644 index 9dc81de9..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3001.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3003.png b/NadekoBot/bin/Debug/data/lol/items/3003.png deleted file mode 100644 index dddc32b1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3003.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3004.png b/NadekoBot/bin/Debug/data/lol/items/3004.png deleted file mode 100644 index 5100aba7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3004.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3006.png b/NadekoBot/bin/Debug/data/lol/items/3006.png deleted file mode 100644 index de540600..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3006.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3007.png b/NadekoBot/bin/Debug/data/lol/items/3007.png deleted file mode 100644 index 97ae6a8c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3007.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3008.png b/NadekoBot/bin/Debug/data/lol/items/3008.png deleted file mode 100644 index 793806b8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3008.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3009.png b/NadekoBot/bin/Debug/data/lol/items/3009.png deleted file mode 100644 index c924de99..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3009.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3010.png b/NadekoBot/bin/Debug/data/lol/items/3010.png deleted file mode 100644 index 45054aba..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3010.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3020.png b/NadekoBot/bin/Debug/data/lol/items/3020.png deleted file mode 100644 index 5ada8577..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3020.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3022.png b/NadekoBot/bin/Debug/data/lol/items/3022.png deleted file mode 100644 index 5c79041a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3022.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3024.png b/NadekoBot/bin/Debug/data/lol/items/3024.png deleted file mode 100644 index 54f50a90..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3024.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3025.png b/NadekoBot/bin/Debug/data/lol/items/3025.png deleted file mode 100644 index a50ce86a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3025.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3026.png b/NadekoBot/bin/Debug/data/lol/items/3026.png deleted file mode 100644 index 8785ff5b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3026.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3027.png b/NadekoBot/bin/Debug/data/lol/items/3027.png deleted file mode 100644 index 00af69d5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3027.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3028.png b/NadekoBot/bin/Debug/data/lol/items/3028.png deleted file mode 100644 index e14679ba..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3028.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3029.png b/NadekoBot/bin/Debug/data/lol/items/3029.png deleted file mode 100644 index e421aaa8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3029.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3031.png b/NadekoBot/bin/Debug/data/lol/items/3031.png deleted file mode 100644 index c77042ef..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3031.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3033.png b/NadekoBot/bin/Debug/data/lol/items/3033.png deleted file mode 100644 index e5232711..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3033.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3034.png b/NadekoBot/bin/Debug/data/lol/items/3034.png deleted file mode 100644 index 6b3d2d80..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3034.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3035.png b/NadekoBot/bin/Debug/data/lol/items/3035.png deleted file mode 100644 index 47b335e8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3035.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3036.png b/NadekoBot/bin/Debug/data/lol/items/3036.png deleted file mode 100644 index 187e3eba..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3036.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3040.png b/NadekoBot/bin/Debug/data/lol/items/3040.png deleted file mode 100644 index e60e0ce2..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3040.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3041.png b/NadekoBot/bin/Debug/data/lol/items/3041.png deleted file mode 100644 index b8df18f1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3041.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3042.png b/NadekoBot/bin/Debug/data/lol/items/3042.png deleted file mode 100644 index 50f96f8e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3042.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3043.png b/NadekoBot/bin/Debug/data/lol/items/3043.png deleted file mode 100644 index 50f96f8e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3043.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3044.png b/NadekoBot/bin/Debug/data/lol/items/3044.png deleted file mode 100644 index 75ec0fab..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3044.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3046.png b/NadekoBot/bin/Debug/data/lol/items/3046.png deleted file mode 100644 index 49d33ce3..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3046.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3047.png b/NadekoBot/bin/Debug/data/lol/items/3047.png deleted file mode 100644 index 906cfe94..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3047.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3048.png b/NadekoBot/bin/Debug/data/lol/items/3048.png deleted file mode 100644 index e60e0ce2..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3048.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3050.png b/NadekoBot/bin/Debug/data/lol/items/3050.png deleted file mode 100644 index 46e040f2..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3050.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3052.png b/NadekoBot/bin/Debug/data/lol/items/3052.png deleted file mode 100644 index e34101a8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3052.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3053.png b/NadekoBot/bin/Debug/data/lol/items/3053.png deleted file mode 100644 index 6911a1dd..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3053.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3056.png b/NadekoBot/bin/Debug/data/lol/items/3056.png deleted file mode 100644 index a50fd171..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3056.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3057.png b/NadekoBot/bin/Debug/data/lol/items/3057.png deleted file mode 100644 index 7174f08d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3057.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3060.png b/NadekoBot/bin/Debug/data/lol/items/3060.png deleted file mode 100644 index 5197835a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3060.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3065.png b/NadekoBot/bin/Debug/data/lol/items/3065.png deleted file mode 100644 index 07c5e0f1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3065.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3067.png b/NadekoBot/bin/Debug/data/lol/items/3067.png deleted file mode 100644 index 04d91da5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3067.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3068.png b/NadekoBot/bin/Debug/data/lol/items/3068.png deleted file mode 100644 index e5a82848..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3068.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3069.png b/NadekoBot/bin/Debug/data/lol/items/3069.png deleted file mode 100644 index 39b1fefd..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3069.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3070.png b/NadekoBot/bin/Debug/data/lol/items/3070.png deleted file mode 100644 index a39bf2ff..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3070.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3071.png b/NadekoBot/bin/Debug/data/lol/items/3071.png deleted file mode 100644 index cf9f3c3a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3071.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3072.png b/NadekoBot/bin/Debug/data/lol/items/3072.png deleted file mode 100644 index 0423bfd6..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3072.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3073.png b/NadekoBot/bin/Debug/data/lol/items/3073.png deleted file mode 100644 index 6714c806..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3073.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3074.png b/NadekoBot/bin/Debug/data/lol/items/3074.png deleted file mode 100644 index 1cdcdc05..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3074.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3075.png b/NadekoBot/bin/Debug/data/lol/items/3075.png deleted file mode 100644 index 2fc7079b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3075.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3077.png b/NadekoBot/bin/Debug/data/lol/items/3077.png deleted file mode 100644 index c9e1aac1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3077.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3078.png b/NadekoBot/bin/Debug/data/lol/items/3078.png deleted file mode 100644 index ad89bb1d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3078.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3082.png b/NadekoBot/bin/Debug/data/lol/items/3082.png deleted file mode 100644 index 9544ff3e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3082.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3083.png b/NadekoBot/bin/Debug/data/lol/items/3083.png deleted file mode 100644 index 61750b16..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3083.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3084.png b/NadekoBot/bin/Debug/data/lol/items/3084.png deleted file mode 100644 index f6ffc81f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3084.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3085.png b/NadekoBot/bin/Debug/data/lol/items/3085.png deleted file mode 100644 index 6adc397e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3085.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3086.png b/NadekoBot/bin/Debug/data/lol/items/3086.png deleted file mode 100644 index 04171d4c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3086.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3087.png b/NadekoBot/bin/Debug/data/lol/items/3087.png deleted file mode 100644 index 85e4da30..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3087.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3089.png b/NadekoBot/bin/Debug/data/lol/items/3089.png deleted file mode 100644 index e66394be..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3089.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3090.png b/NadekoBot/bin/Debug/data/lol/items/3090.png deleted file mode 100644 index 6d8a4c04..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3090.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3091.png b/NadekoBot/bin/Debug/data/lol/items/3091.png deleted file mode 100644 index 4ae88d37..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3091.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3092.png b/NadekoBot/bin/Debug/data/lol/items/3092.png deleted file mode 100644 index 72a9762c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3092.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3094.png b/NadekoBot/bin/Debug/data/lol/items/3094.png deleted file mode 100644 index 9e03a07c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3094.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3096.png b/NadekoBot/bin/Debug/data/lol/items/3096.png deleted file mode 100644 index c871c95f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3096.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3097.png b/NadekoBot/bin/Debug/data/lol/items/3097.png deleted file mode 100644 index 25a51873..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3097.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3098.png b/NadekoBot/bin/Debug/data/lol/items/3098.png deleted file mode 100644 index f74337d2..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3098.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3100.png b/NadekoBot/bin/Debug/data/lol/items/3100.png deleted file mode 100644 index 92dc5419..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3100.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3101.png b/NadekoBot/bin/Debug/data/lol/items/3101.png deleted file mode 100644 index 024b48b8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3101.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3102.png b/NadekoBot/bin/Debug/data/lol/items/3102.png deleted file mode 100644 index a6dee161..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3102.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3104.png b/NadekoBot/bin/Debug/data/lol/items/3104.png deleted file mode 100644 index 70fa827b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3104.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3105.png b/NadekoBot/bin/Debug/data/lol/items/3105.png deleted file mode 100644 index 22910878..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3105.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3108.png b/NadekoBot/bin/Debug/data/lol/items/3108.png deleted file mode 100644 index f53ef63e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3108.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3110.png b/NadekoBot/bin/Debug/data/lol/items/3110.png deleted file mode 100644 index 1d56e5ad..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3110.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3111.png b/NadekoBot/bin/Debug/data/lol/items/3111.png deleted file mode 100644 index d5b256cd..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3111.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3112.png b/NadekoBot/bin/Debug/data/lol/items/3112.png deleted file mode 100644 index 485eda6f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3112.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3113.png b/NadekoBot/bin/Debug/data/lol/items/3113.png deleted file mode 100644 index dd395536..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3113.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3114.png b/NadekoBot/bin/Debug/data/lol/items/3114.png deleted file mode 100644 index b09801b2..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3114.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3115.png b/NadekoBot/bin/Debug/data/lol/items/3115.png deleted file mode 100644 index ad34c51f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3115.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3116.png b/NadekoBot/bin/Debug/data/lol/items/3116.png deleted file mode 100644 index 942b8a03..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3116.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3117.png b/NadekoBot/bin/Debug/data/lol/items/3117.png deleted file mode 100644 index 74ef464f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3117.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3122.png b/NadekoBot/bin/Debug/data/lol/items/3122.png deleted file mode 100644 index 880b7c5d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3122.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3123.png b/NadekoBot/bin/Debug/data/lol/items/3123.png deleted file mode 100644 index 0fc108a6..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3123.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3124.png b/NadekoBot/bin/Debug/data/lol/items/3124.png deleted file mode 100644 index 440ed28d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3124.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3133.png b/NadekoBot/bin/Debug/data/lol/items/3133.png deleted file mode 100644 index 78589d86..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3133.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3134.png b/NadekoBot/bin/Debug/data/lol/items/3134.png deleted file mode 100644 index a734aa55..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3134.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3135.png b/NadekoBot/bin/Debug/data/lol/items/3135.png deleted file mode 100644 index d0978699..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3135.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3136.png b/NadekoBot/bin/Debug/data/lol/items/3136.png deleted file mode 100644 index 27c365a5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3136.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3137.png b/NadekoBot/bin/Debug/data/lol/items/3137.png deleted file mode 100644 index 5b416056..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3137.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3139.png b/NadekoBot/bin/Debug/data/lol/items/3139.png deleted file mode 100644 index b0b06ba9..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3139.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3140.png b/NadekoBot/bin/Debug/data/lol/items/3140.png deleted file mode 100644 index e43f911c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3140.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3142.png b/NadekoBot/bin/Debug/data/lol/items/3142.png deleted file mode 100644 index 8b12287d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3142.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3143.png b/NadekoBot/bin/Debug/data/lol/items/3143.png deleted file mode 100644 index bf6b55d6..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3143.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3144.png b/NadekoBot/bin/Debug/data/lol/items/3144.png deleted file mode 100644 index d3cee8db..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3144.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3145.png b/NadekoBot/bin/Debug/data/lol/items/3145.png deleted file mode 100644 index 86432b68..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3145.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3146.png b/NadekoBot/bin/Debug/data/lol/items/3146.png deleted file mode 100644 index 22754c8a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3146.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3147.png b/NadekoBot/bin/Debug/data/lol/items/3147.png deleted file mode 100644 index 6bad413d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3147.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3151.png b/NadekoBot/bin/Debug/data/lol/items/3151.png deleted file mode 100644 index 896016ac..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3151.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3152.png b/NadekoBot/bin/Debug/data/lol/items/3152.png deleted file mode 100644 index 36350649..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3152.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3153.png b/NadekoBot/bin/Debug/data/lol/items/3153.png deleted file mode 100644 index 1f6d785a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3153.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3154.png b/NadekoBot/bin/Debug/data/lol/items/3154.png deleted file mode 100644 index 8a36ff28..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3154.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3155.png b/NadekoBot/bin/Debug/data/lol/items/3155.png deleted file mode 100644 index 6398a799..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3155.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3156.png b/NadekoBot/bin/Debug/data/lol/items/3156.png deleted file mode 100644 index fc528242..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3156.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3157.png b/NadekoBot/bin/Debug/data/lol/items/3157.png deleted file mode 100644 index e1b24329..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3157.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3158.png b/NadekoBot/bin/Debug/data/lol/items/3158.png deleted file mode 100644 index 020474c0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3158.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3165.png b/NadekoBot/bin/Debug/data/lol/items/3165.png deleted file mode 100644 index 0a24c521..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3165.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3170.png b/NadekoBot/bin/Debug/data/lol/items/3170.png deleted file mode 100644 index b6377dd8..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3170.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3174.png b/NadekoBot/bin/Debug/data/lol/items/3174.png deleted file mode 100644 index 9ea6b116..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3174.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3175.png b/NadekoBot/bin/Debug/data/lol/items/3175.png deleted file mode 100644 index 6bc36062..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3175.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3180.png b/NadekoBot/bin/Debug/data/lol/items/3180.png deleted file mode 100644 index 3c1ff663..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3180.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3181.png b/NadekoBot/bin/Debug/data/lol/items/3181.png deleted file mode 100644 index fd8dba22..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3181.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3184.png b/NadekoBot/bin/Debug/data/lol/items/3184.png deleted file mode 100644 index 6314ff5c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3184.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3185.png b/NadekoBot/bin/Debug/data/lol/items/3185.png deleted file mode 100644 index 535d1be1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3185.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3187.png b/NadekoBot/bin/Debug/data/lol/items/3187.png deleted file mode 100644 index 4f1f10fa..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3187.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3190.png b/NadekoBot/bin/Debug/data/lol/items/3190.png deleted file mode 100644 index d172b517..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3190.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3191.png b/NadekoBot/bin/Debug/data/lol/items/3191.png deleted file mode 100644 index 5f0bf7fa..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3191.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3196.png b/NadekoBot/bin/Debug/data/lol/items/3196.png deleted file mode 100644 index 03426548..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3196.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3197.png b/NadekoBot/bin/Debug/data/lol/items/3197.png deleted file mode 100644 index 1bb9021e..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3197.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3198.png b/NadekoBot/bin/Debug/data/lol/items/3198.png deleted file mode 100644 index a0a0c3f5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3198.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3200.png b/NadekoBot/bin/Debug/data/lol/items/3200.png deleted file mode 100644 index 0eae6d0a..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3200.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3211.png b/NadekoBot/bin/Debug/data/lol/items/3211.png deleted file mode 100644 index 2298bfa5..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3211.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3222.png b/NadekoBot/bin/Debug/data/lol/items/3222.png deleted file mode 100644 index aff1241c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3222.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3240.png b/NadekoBot/bin/Debug/data/lol/items/3240.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3240.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3241.png b/NadekoBot/bin/Debug/data/lol/items/3241.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3241.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3242.png b/NadekoBot/bin/Debug/data/lol/items/3242.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3242.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3243.png b/NadekoBot/bin/Debug/data/lol/items/3243.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3243.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3285.png b/NadekoBot/bin/Debug/data/lol/items/3285.png deleted file mode 100644 index c95892c4..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3285.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3301.png b/NadekoBot/bin/Debug/data/lol/items/3301.png deleted file mode 100644 index b53e1cb1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3301.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3302.png b/NadekoBot/bin/Debug/data/lol/items/3302.png deleted file mode 100644 index fa58ff60..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3302.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3303.png b/NadekoBot/bin/Debug/data/lol/items/3303.png deleted file mode 100644 index d6c4455b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3303.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3340.png b/NadekoBot/bin/Debug/data/lol/items/3340.png deleted file mode 100644 index 422209e9..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3340.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3341.png b/NadekoBot/bin/Debug/data/lol/items/3341.png deleted file mode 100644 index 35faa893..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3341.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3345.png b/NadekoBot/bin/Debug/data/lol/items/3345.png deleted file mode 100644 index a35c76ad..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3345.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3348.png b/NadekoBot/bin/Debug/data/lol/items/3348.png deleted file mode 100644 index 4f1f10fa..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3348.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3361.png b/NadekoBot/bin/Debug/data/lol/items/3361.png deleted file mode 100644 index ae46a42f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3361.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3362.png b/NadekoBot/bin/Debug/data/lol/items/3362.png deleted file mode 100644 index ece4c255..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3362.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3363.png b/NadekoBot/bin/Debug/data/lol/items/3363.png deleted file mode 100644 index f166c456..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3363.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3364.png b/NadekoBot/bin/Debug/data/lol/items/3364.png deleted file mode 100644 index a7b03d5d..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3364.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3401.png b/NadekoBot/bin/Debug/data/lol/items/3401.png deleted file mode 100644 index 1f511741..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3401.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3410.png b/NadekoBot/bin/Debug/data/lol/items/3410.png deleted file mode 100644 index 6bc36062..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3410.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3416.png b/NadekoBot/bin/Debug/data/lol/items/3416.png deleted file mode 100644 index 6bc36062..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3416.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3422.png b/NadekoBot/bin/Debug/data/lol/items/3422.png deleted file mode 100644 index 6bc36062..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3422.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3455.png b/NadekoBot/bin/Debug/data/lol/items/3455.png deleted file mode 100644 index 6bc36062..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3455.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3460.png b/NadekoBot/bin/Debug/data/lol/items/3460.png deleted file mode 100644 index fb909636..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3460.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3461.png b/NadekoBot/bin/Debug/data/lol/items/3461.png deleted file mode 100644 index 05075e48..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3461.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3504.png b/NadekoBot/bin/Debug/data/lol/items/3504.png deleted file mode 100644 index adcc7231..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3504.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3508.png b/NadekoBot/bin/Debug/data/lol/items/3508.png deleted file mode 100644 index 93252544..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3508.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3512.png b/NadekoBot/bin/Debug/data/lol/items/3512.png deleted file mode 100644 index d5970d61..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3512.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3599.png b/NadekoBot/bin/Debug/data/lol/items/3599.png deleted file mode 100644 index 67eb05bd..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3599.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3671.png b/NadekoBot/bin/Debug/data/lol/items/3671.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3671.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3672.png b/NadekoBot/bin/Debug/data/lol/items/3672.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3672.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3673.png b/NadekoBot/bin/Debug/data/lol/items/3673.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3673.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3674.png b/NadekoBot/bin/Debug/data/lol/items/3674.png deleted file mode 100644 index 14c44f2c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3674.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3706.png b/NadekoBot/bin/Debug/data/lol/items/3706.png deleted file mode 100644 index cd57813c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3706.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3711.png b/NadekoBot/bin/Debug/data/lol/items/3711.png deleted file mode 100644 index 346114d0..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3711.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3715.png b/NadekoBot/bin/Debug/data/lol/items/3715.png deleted file mode 100644 index 50873784..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3715.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3742.png b/NadekoBot/bin/Debug/data/lol/items/3742.png deleted file mode 100644 index 30d38888..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3742.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3748.png b/NadekoBot/bin/Debug/data/lol/items/3748.png deleted file mode 100644 index 3fed85c1..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3748.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3751.png b/NadekoBot/bin/Debug/data/lol/items/3751.png deleted file mode 100644 index 173ea68b..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3751.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3800.png b/NadekoBot/bin/Debug/data/lol/items/3800.png deleted file mode 100644 index 4324c6de..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3800.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3801.png b/NadekoBot/bin/Debug/data/lol/items/3801.png deleted file mode 100644 index d7aff5df..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3801.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3812.png b/NadekoBot/bin/Debug/data/lol/items/3812.png deleted file mode 100644 index 5b87d050..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3812.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3901.png b/NadekoBot/bin/Debug/data/lol/items/3901.png deleted file mode 100644 index 68c48401..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3901.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3902.png b/NadekoBot/bin/Debug/data/lol/items/3902.png deleted file mode 100644 index 5d53fd9c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3902.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3903.png b/NadekoBot/bin/Debug/data/lol/items/3903.png deleted file mode 100644 index 8bdfdd7c..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3903.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3930.png b/NadekoBot/bin/Debug/data/lol/items/3930.png deleted file mode 100644 index 81a1e5f7..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3930.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3931.png b/NadekoBot/bin/Debug/data/lol/items/3931.png deleted file mode 100644 index dbdef393..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3931.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3932.png b/NadekoBot/bin/Debug/data/lol/items/3932.png deleted file mode 100644 index 597827bd..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3932.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/3933.png b/NadekoBot/bin/Debug/data/lol/items/3933.png deleted file mode 100644 index ee0a4d8f..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/3933.png and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/lol/items/Thumbs.db b/NadekoBot/bin/Debug/data/lol/items/Thumbs.db deleted file mode 100644 index edce7e32..00000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/Thumbs.db and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/quotes.json b/NadekoBot/bin/Debug/data/quotes.json deleted file mode 100644 index 47f3e1ab..00000000 --- a/NadekoBot/bin/Debug/data/quotes.json +++ /dev/null @@ -1,874 +0,0 @@ -[ - { - "Text": "Kill them all, God will recognise his own.", - "Author": "Arnaud Amaury, Abbot of Citeaux" - }, - { - "Text": "Nothing is to be feared but fear.", - "Author": "Francis Bacon" - }, - { - "Text": "In order for a war to be just, three things are necessary. First, the authority of the sovereign. Secondly, a just cause. Thirdly, a rightful intention.", - "Author": "Thomas Aquinas" - }, - { - "Text": "The fields have eyes, and the woods have ears.", - "Author": "Geoffrey Chaucer, Canterbury Tales, The Knights Tale" - }, - { - "Text": "Following the light of the sun, we left the Old World.", - "Author": "Christopher Columbus" - }, - { - "Text": "War is delightful to those who have had no experience of it.", - "Author": "Erasmus" - }, - { - "Text": "Fortune favours the audacious.", - "Author": "Erasmus" - }, - { - "Text": "The most disadvantageous peace is better than the most just war.", - "Author": "Erasmus" - }, - { - "Text": "If you do not leave this pasturage, Saladin will come and attack you here. And if you retreat from this attack the shame and reproach will be very great.", - "Author": "Gerard of Ridefort, letter written to King Guy" - }, - { - "Text": "The strength of God will enable us, a small but faithful band, to overcome the multitude of the faithless.", - "Author": "Robert Guiscard" - }, - { - "Text": "It is better to live one day as a lion than a hundred years as a sheep.", - "Author": "Italian proverb" - }, - { - "Text": "Do you know, my son, with what little understanding the world is ruled?", - "Author": "Pope Julius III" - }, - { - "Text": "A safe stronghold our God is still. A trusty shield and weapon.", - "Author": "Martin Luther" - }, - { - "Text": "Faith must trample under foot all reason, sense, and understanding.", - "Author": "Martin Luther" - }, - { - "Text": "Nothing good ever comes of violence.", - "Author": "Martin Luther" - }, - { - "Text": "War is the greatest plague that can affect humanity; it destroys religion, it destroys states, it destroys families. Any scourge is preferable to it.", - "Author": "Martin Luther" - }, - { - "Text": "Since love and fear can hardly coexist together, if we must choose between them, it is far safer to be feared than loved.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "The princes who have done great things are the ones who have taken little account of their promises.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "So far as he is able, a prince should stick to the path of good but, if the necessity arises, he should know how to follow evil.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "Men should either be treated generously or destroyed, because they take revenge for slight injures - for heavy ones they cannot.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "The prince must be a fox... to recognize the traps and a lion to frighten the wolves.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "The sinews of war are not gold, but good soldiers.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "Among other evils which being unarmed brings you, it causes you to be despised.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "The best fortress which a prince can possess is the affection of his people.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "No enterprise is more likely to succeed than one concealed from the enemy until it is ripe for execution.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "A prince should therefore have no other aim or thought... but war and its organisation and discipline.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "Whoever conquers a free town and does not demolish it commits a great error and may expect to be ruined himself.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "Good order and discipline in any army are to be depended upon more than courage alone.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "Good order makes men bold, and confusion, cowards.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "He who wishes to be obeyed must know how to command.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "One should never risk one's whole fortune unless supported by one's entire forces.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "It is not titles that honour men, but men that honour titles.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "To ensure victory the troops must have confidence in themselves as well as in their commanders.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "Strike up the drum and march courageously.", - "Author": "Christopher Marlowe" - }, - { - "Text": "Better to reign in hell than serve in heav'n.", - "Author": "John Milton" - }, - { - "Text": "For what can war, but endless war, still breed?", - "Author": "John Milton" - }, - { - "Text": "Will no one revenge me of the injuries I have sustained from one turbulent priest?", - "Author": "Henry II, King of England" - }, - { - "Text": "Luck is the residue of design.", - "Author": "John Milton" - }, - { - "Text": "Peace hath her victories, no less renowned than War.", - "Author": "John Milton" - }, - { - "Text": "What does not destroy me, makes me strong.", - "Author": "John Milton" - }, - { - "Text": "If some among them are innocent, it is expedient that they should be assayed like gold in the furnace and purged by proper judicial examination.", - "Author": "Royal letter opening the enquiry into the Templar Knights" - }, - { - "Text": "He doth nothing but talk of his horse.", - "Author": "Shakespeare, The Merchant of Venice, i, 2" - }, - { - "Text": "All the world's a stage, And all the men and women merely players.", - "Author": "Shakespeare, As You Like It, ii, 7" - }, - { - "Text": "I will kill thee a hundred and fifty ways.", - "Author": "Shakespeare, As You Like It, v, 1" - }, - { - "Text": "Here I and sorrows sit; Here is my throne, bid kings come bow to it.", - "Author": "Shakespeare, King John, iii, 1" - }, - { - "Text": "Go, bid the soldiers shoot.", - "Author": "Shakespeare, Hamlet, iv, 2" - }, - { - "Text": "Come the three corners of the world in arms, and we shall shock them.", - "Author": "Shakespeare, King John, iv, 7" - }, - { - "Text": "A man can die but once.", - "Author": "Shakespeare, King Henry IV, Part:II. iii, 2" - }, - { - "Text": "Give me another horse: bind up my wounds.", - "Author": "Shakespeare, King Richard III, v, 3" - }, - { - "Text": "A horse! A horse! My kingdom for a horse!", - "Author": "Shakespeare, King Richard III, v, 4" - }, - { - "Text": "The better part of valour is discretion.", - "Author": "Shakespeare, King Henry IV, Part:I, v, 4" - }, - { - "Text": "To whom God will, there be the victory.", - "Author": "Shakespeare, King Henry the Sixth, Part:III, ii, 5" - }, - { - "Text": "A victory is twice itself when the achiever brings home full numbers.", - "Author": "Shakespeare, Much Ado About Nothing, i, 1" - }, - { - "Text": "We are ready to try our fortunes To the last man.", - "Author": "Shakespeare, King Henry IV, Part:II, iv, 2" - }, - { - "Text": "And many strokes, though with a little axe, hew down and fell the hardest-timbered oak.", - "Author": "Shakespeare, King Henry VI, Part:III, ii, 1" - }, - { - "Text": "Upon his royal face there is no note how dread an army hath enrounded him;", - "Author": "Shakespeare, King Henry V, iv prologue" - }, - { - "Text": "There's daggers in men's smiles.", - "Author": "Shakespeare, Macbeth, II, 3" - }, - { - "Text": "It is better to be on hand with ten men than absent with ten thousand.", - "Author": "Tamerlane" - }, - { - "Text": "And ye shall hear of wars and rumours of wars, see that ye be not troubled; for all these things must come to pass, but the end is not yet.", - "Author": "Matthew, ch. XXIV, V.6" - }, - { - "Text": "Blessed be the Lord my strength, which teacheth my hands to war, and my fingers to fight.", - "Author": "Psalm, CXLIV" - }, - { - "Text": "Inflict not on an enemy every injury in your power, for he may afterwards become your friend.", - "Author": "Moslih Eddin Saadi" - }, - { - "Text": "No one conquers who doesn't fight.", - "Author": "Gabriel Biel" - }, - { - "Text": "An emperor is subject to no one but God and justice.", - "Author": "Fredrick I, Barbarossa" - }, - { - "Text": "Wars begin when you will, but they do not end when you please.", - "Author": "Niccoló Machiavelli" - }, - { - "Text": "Let the boy win his spurs.", - "Author": "Edward III, King of England, Battle of Crecy 1345" - }, - { - "Text": "We who are the rest of the people raised our heart and eyes to heaven crying for God to have compassion upon us, and to turn away from us the power of the French.", - "Author": "Thomas Elthem, Henry V's Chaplain at Agincourt" - }, - { - "Text": "There are some defeats more triumphant than victories.", - "Author": "Michel Eyquem de Montaigne" - }, - { - "Text": "Tis so much to be a king, that he only is so by being so.", - "Author": "Michel Eyquem de Montaigne" - }, - { - "Text": "The souls of emperors and cobblers are cast in the same mould. ...The same reason that makes us wrangle with a neighbour causes a war betwixt princes.", - "Author": "Michel Eyquem de Montaigne" - }, - { - "Text": "He who knows not how to dissimulate, can not reign.", - "Author": "Louis XI, King of France" - }, - { - "Text": "So many great nobles, things, administrations, so many high chieftains, so many brave nations, so many proud princes, and power so splendid; In a moment, a twinkling, all utterly ended.", - "Author": "Jacobus de Benedictus" - }, - { - "Text": "It is more honourable to be raised to a throne than to be born to one. Fortune bestows the one, merit obtains the other.", - "Author": "Petrarch" - }, - { - "Text": "He who defends everything defends nothing.", - "Author": "Fredrick II, Holy Roman Emperor" - }, - { - "Text": "I am the King of Rome, and above grammar.", - "Author": "Sigismund, Holy Roman Emperor" - }, - { - "Text": "When you see contention amongst your enemies, go and sit at ease with your friends; but when you see them of one mind, string your bow, and place stones upon the ramparts.", - "Author": "Moslih Eddin Saadi" - }, - { - "Text": "Disasters teach us humility.", - "Author": "Saint Anselm, Archbishop of Canterbury" - }, - { - "Text": "I have loved justice and hated inequity; and therefore I die in exile.", - "Author": "Pope Gregory VII, Tuscan Pope" - }, - { - "Text": "It is easy to be brave behind a castle wall.", - "Author": "Welsh proverb" - }, - { - "Text": "In a fight, anger is as good as courage.", - "Author": "Welsh proverb" - }, - { - "Text": "Undertake this journey for the remission of your sins, with the assurance of the imperishable glory of the Kingdom of Heaven!", - "Author": "Urban II, Pope" - }, - { - "Text": "To carry on war, three things are necessary: money, money, and yet more money.", - "Author": "Gian Jacopo Trivulzio" - }, - { - "Text": "Fight to the last gasp.", - "Author": "Shakespeare, King Henry VI, part 1, i, 1" - }, - { - "Text": "I'll fight, till from my bones my flesh be hacked.", - "Author": "Shakespeare, Macbeth, v, 3" - }, - { - "Text": "Once more unto the breach, dear friends, once more!", - "Author": "Shakespeare, King Henry V, iii, 1" - }, - { - "Text": "Let the world tremble as it senses all you are about to accomplish.", - "Author": "Luis Camóes, The Lusóads, canto 1:15" - }, - { - "Text": "Having done everything practical to make ready for so long a voyage, we prepared our souls to meet death, which is always on a sailor's horizon.", - "Author": "Luis Camóes, The Lusóads, canto 4:86" - }, - { - "Text": "I speak Spanish to God, Italian to women, French to men, and German to my horse.", - "Author": "King Charles V King of France" - }, - { - "Text": "Had I been present at the creation, I would have given some useful hints for the better ordering of the universe.", - "Author": "Alfonso X, the Wise, King of Castile" - }, - { - "Text": "Every man should arm himself as quickly as he could, and come to the King.", - "Author": "Charles Oman" - }, - { - "Text": "Let those who once fought against brothers and relatives now rightfully fight against barbarians.", - "Author": "Pope Urban II" - }, - { - "Text": "On both sides the troops were commanded by royal princes and they massacred each other mercilessly.", - "Author": "Matthew of Edessa" - }, - { - "Text": "The fighting was fierce and lasted for the greater part of a day; blood ran in rivers.", - "Author": "Matthew of Edessa" - }, - { - "Text": "Many of the common people in the armies were desolate, fearing future poverty; and so they sold their bows and the cowards returned to their own homes.", - "Author": "Fulcher of Chartres" - }, - { - "Text": "Let anyone who has zeal for God come with me! Let us fight for our brothers! Let Heaven's will be done!", - "Author": "Conrad III, ruler of the Holy Roman Empire" - }, - { - "Text": "God has aroused the spirit of kings and princes to root up from the earth the enemies of the Christian name.", - "Author": "Bernard of Clairvaux" - }, - { - "Text": "Whoever devotedly undertakes and performs this most holy journey...shall have the enjoyment of eternal reward from the repayer of all men.", - "Author": "Pope Eugenius III" - }, - { - "Text": "Surrender before you all die by the sword, for I do not wish you to perish.", - "Author": "Imad ad-Din Zanghi" - }, - { - "Text": "We shall not surrender.", - "Author": "Archbishop Hugh" - }, - { - "Text": "Come on soldiers! Guardians and agents of the supreme law! Here is a sacrifice of dogs ready for your swords!", - "Author": "Il-Ghazi" - }, - { - "Text": "At the first sound of the bugle, everyone should make haste to put on arms and armour.", - "Author": "Walter, Chancellor to Roger of Salerno" - }, - { - "Text": "Put an end to so great an evil and arrive at a peace settlement whatever the outcome, and whatever the conditions.", - "Author": "William of Tyre" - }, - { - "Text": "Take up the weapons of the glorious army for the salvation of many thousands.", - "Author": "Adela, wife of Stephen of Blois" - }, - { - "Text": "If they wish to fight today, let them come like men.", - "Author": "Bohemund" - }, - { - "Text": "They assembled from all sides, one after another, with arms and horses and all the panoply of war...", - "Author": "Anna Comnenus, The Alexiad" - }, - { - "Text": "Alas! How many noble and valiant knights we lost.", - "Author": "Fulcher of Chartres" - }, - { - "Text": "There is now no hope of escaping. If you fight you will conquer, but if you flee you will fall.", - "Author": "Fulcher of Chartres" - }, - { - "Text": "When he caught sight of their army, he was terrified and groaned in his mind.", - "Author": "Fulcher of Chartres" - }, - { - "Text": "The Frankish duke wept bitterly to see his soldiers massacred.", - "Author": "Matthew of Edessa" - }, - { - "Text": "He withdrew, himself wounded, and was compelled to return home inglorious, weeping - he who had once vainly hoped for the glory of a triumph.", - "Author": "William of Apulia" - }, - { - "Text": "Therefore gird yourselves manfully and take up joyful arms for the name of Christ.", - "Author": "Bernard of Clairvaux" - }, - { - "Text": "Set out on pilgrimage and triumph gloriously over the infidels in the East.", - "Author": "Orderic Vitalis" - }, - { - "Text": "Following the light of the sun, we left the Old World.", - "Author": "Christopher Columbus" - }, - { - "Text": "You can't wake a person who is pretending to be asleep.", - "Author": "Navajo proverb" - }, - { - "Text": "You can't win them all.", - "Author": "Navajo proverb" - }, - { - "Text": "There is nothing as eloquent as a rattlesnake's tail.", - "Author": "Navajo proverb" - }, - { - "Text": "Wisdom comes only when you stop looking for it and start living the life the Creator intended for you.", - "Author": "Hopi proverb" - }, - { - "Text": "You cannot see the future with tears in your eyes.", - "Author": "Navajo proverb" - }, - { - "Text": "The one who tells the stories rules the world.", - "Author": "Hopi proverb" - }, - { - "Text": "One finger cannot lift a pebble.", - "Author": "Hopi proverb" - }, - { - "Text": "In death, I am born.", - "Author": "Hopi proverb" - }, - { - "Text": "Walk lightly in the spring; Mother Earth is pregnant.", - "Author": "Kiowa proverb" - }, - { - "Text": "After dark all cats are leopards.", - "Author": "Zuni proverb" - }, - { - "Text": "A good man does not take what belongs to someone else.", - "Author": "Pueblo proverb" - }, - { - "Text": "Cherish youth, but trust old age.", - "Author": "Pueblo proverb" - }, - { - "Text": "Force, no matter how concealed, begets resistance.", - "Author": "Lakota proverb" - }, - { - "Text": "The weakness of the enemy makes our strength.", - "Author": "Cherokee proverb" - }, - { - "Text": "All who have died are equal.", - "Author": "Comanche proverb" - }, - { - "Text": "We dared not charge them except all together... For they were so numerous that they could have blinded us with clods of earth if God, of His great mercy, had not aided and protected us.", - "Author": "Bernal Díaz del Castillo" - }, - { - "Text": "Then Cortés told them that the King's laws decreed such treachery should not go unpunished, and that they must die for their crime.", - "Author": "Bernal Díaz del Castillo" - }, - { - "Text": "...Then he ordered a musket to be fired, which was the signal we had agreed on; and they received a blow they will remember for ever, for we killed many of them, and the promises of their false idols were of no avail.", - "Author": "Bernal Díaz del Castillo" - }, - { - "Text": "And when we saw all those cities and villages built in the water, and other great towns on dry land, and that straight and level causeway leading to Mexico, we were astounded...", - "Author": "Bernal Díaz del Castillo" - }, - { - "Text": "It was all so wonderful that I do not know how to describe this first glimpse of things never heard of, seen or dreamed of before...", - "Author": "Bernal Díaz del Castillo" - }, - { - "Text": "As for us, we were scarcely four hundred strong, and we well remembered the word and warning...we had received to beware of entering the city of Mexico, since they would kill us as soon as they had us inside.", - "Author": "Bernal Díaz del Castillo" - }, - { - "Text": "What men in all the world have shown such daring?", - "Author": "Hernan Cortes" - }, - { - "Text": "For I assure Your Majesty that if God had not mysteriously assisted us and the victory had gone to Narváez, it would have been the greatest harm that Spaniards had done to each other for a long time past.", - "Author": "Hernan Cortes" - }, - { - "Text": "Furthermore, they had calculated that if 25,000 of them died for every one of us, they would finish with us first, for they were many and we were but few.", - "Author": "Hernan Cortes" - }, - { - "Text": "These Aztecs then came and I told them to observe how they could not triumph, and how each day we did them great harm and killed many of them and we were burning and destroying their city.", - "Author": "Hernan Cortes" - }, - { - "Text": "They no longer had nor could find any arrows, javelins or stones with which to attack us...", - "Author": "Hernan Cortes" - }, - { - "Text": "...and our allies fighting with us were armed with swords and bucklers, and slaughtered so many of them on land and in the water that more than forty thousand were killed or taken that day.", - "Author": "Hernan Cortes" - }, - { - "Text": "So loud was the wailing of the women and children that there was not one man among us whose heart did not bleed at the sound...", - "Author": "Hernan Cortes" - }, - { - "Text": "We went there to serve God, and also to get rich.", - "Author": "Bernal Díaz del Castillo" - }, - { - "Text": "It is not good to look at the clouds or your work will not progress.", - "Author": "Mayan proverb" - }, - { - "Text": "A certain bloody man ... who had been chief of brigands in Scotland", - "Author": "Lanercost the chronicler on William Wallace" - }, - { - "Text": "Evil priests are the cause of the people's ruin, so the ruin of the realm of Scotland had its source within the bosom of its own church", - "Author": "Lanercost the chronicler" - }, - { - "Text": "The common folk of the land followed him as their leader and ruler; the retainers of the great lords adhered to him...", - "Author": "Walter of Guisborough on the leadership of William Wallace" - }, - { - "Text": "... and even though the lords themselves were present with the English king in body, at heart they were on the opposite side.", - "Author": "Walter of Guisborough on the leadership of William Wallace" - }, - { - "Text": "From that time there gathered to him all who were of bitter heart and were weighed down beneath the burden of bondage under the intolerable rule of English domination and he became their leader.", - "Author": "Scottish chronicler Fordun on William Wallace" - }, - { - "Text": "My lord if we cross the bridge we are dead men.", - "Author": "Traitorous Scottish knight Richard Lundie at the battle of Stirling Bridge" - }, - { - "Text": "They flayed him and divided pieces of his skin between them, not as keepsakes but out of hatred of him.", - "Author": "Guisborough on the demise of Hugh Cressingham, treasurer of Scotland, at the hands of the Scots'" - }, - { - "Text": "We shall then defeat the whole lot of them in one go!", - "Author": "King Edward I of England upon hearing of his Welsh allies threats to desert to the Scots'" - }, - { - "Text": "I have brought you to the revel, now dance if you can!", - "Author": "William Wallace, rallying his men before the battle of Falkirk" - }, - { - "Text": "Before them and on every hand foul lanes and deep dykes and many hedges with hills and valleys: a right evil place to approach, as could have been devised.", - "Author": "from the 'Arrivall' on the land near Tewkesbury" - }, - { - "Text": "...Henry seeing he could not resist the multitude of the Scots, turned his horse with the intention of returning to his companions; but Robert opposed him and struck him on the head with an axe.", - "Author": "Account of the duel between Robert the Bruce King of Scotland and English knight Henry de Bohun" - }, - { - "Text": "That field hath eyen, and the wood hath ears.", - "Author": "Geoffrey Chaucer, Canterbury Tales. The Knightes Tale." - }, - { - "Text": "Yet in our ashen cold is fire yreken.", - "Author": "Geoffrey Chaucer, Canterbury Tales. The Reves Prologue." - }, - { - "Text": "In his owen grese I made him frie.", - "Author": "Geoffrey Chaucer, Canterbury Tales. The Reves Tale." - }, - { - "Text": "Once more unto the breach, dear friends, once more; Or close the wall up with our English dead!", - "Author": "William Shakespeare, Henry V, scene i." - }, - { - "Text": "Cry 'God for Harry! England and Saint George!", - "Author": "William Shakespeare, Henry V, scene i." - }, - { - "Text": "Will no one revenge me of the injuries I have sustained from one turbulent priest?", - "Author": "Henry II, King of England" - }, - { - "Text": "O, Thou hast damnable iteration; and art, indeed, able to corrupt a saint.", - "Author": "William Shakespeare, Henry IV, part:I, act i, scene ii" - }, - { - "Text": "There live not three good men unhanged in England; and one of them is fat and grows old.", - "Author": "William Shakespeare, Henry IV, part:I, act ii, scene iv" - }, - { - "Text": "We come here with no peaceful intent, but ready for battle, determined to avenge our wrongs and set our country free.", - "Author": "William Wallace, rallying his men before the battle of Stirling Bridge" - }, - { - "Text": "If you're lucky enough to be Irish, then you're lucky enough.", - "Author": "Irish quote" - }, - { - "Text": "It is easy to be brave behind a castle wall.", - "Author": "Welsh proverb" - }, - { - "Text": "In a fight, anger is as good as courage.", - "Author": "Welsh proverb" - }, - { - "Text": "The better part of valour is discretion.", - "Author": "William Shakespeare, Henry IV, part:I, act v, scene iv" - }, - { - "Text": "Castles were built a stone at a time.", - "Author": "Irish proverb" - }, - { - "Text": "A king's son is not nobler than his food.", - "Author": "Irish proverb" - }, - { - "Text": "Drink is the curse of the land. It makes you fight with your neighbour. It makes you shoot at your landlord and it makes you miss him.", - "Author": "Irish proverb" - }, - { - "Text": "A man is a lion in his own cause.", - "Author": "Scottish proverb" - }, - { - "Text": "Twelve highlanders and a bagpipe make a rebellion", - "Author": "Scottish proverb" - }, - { - "Text": "Tuitio Fidei et Obsequium Pauperum - Support the faithful and serve the poor.", - "Author": "Motto of the Knights Hospitaller" - }, - { - "Text": "A mighty persecutor of the Christian name and faith, a just prince, valiant and wise, and according to the traditions of his race, a religious man.", - "Author": "William of Tyre speaking of Nur ad-Din" - }, - { - "Text": "We should sympathize with their grief and in pity spare them, because they have lost a prince such as the rest of the world does not possess today.", - "Author": "Nur ad-Din on the death of King Baldwin III of Jerusalem" - }, - { - "Text": "After Jerusalem had been captured, Saladin had the cross taken down from the Temple of the Lord and, beating it with clubs, had it carried on display for two days throughout the city.", - "Author": "Letter written by Terricus, acting commander of the Templars to Henry II in 1188" - }, - { - "Text": "If some among them are innocent, it is expedient that they should be assayed like gold in the furnace and purged by proper judicial examination.", - "Author": "Royal letter opening the Enquiry into the Templars 1307" - }, - { - "Text": "After entering Asia Minor, the crusaders experienced the treachery of the Greek emperor. Our forces, however, had indulged in certain excesses and had incurred his displeasure.", - "Author": "William of Newburgh On the failure of the 2nd Crusade" - }, - { - "Text": "And in this battle, brother William, Master of the Templars, lost an eye; and he had lost the other on the previous Shrove Tuesday; and that lord died as a consequence, may God absolve him!", - "Author": "John of Joinville 11th February 1250" - }, - { - "Text": "Then he caused the Temple of the lord to be washed with rose water, inside and out, above and below, and, with an astonishing commotion, had his law acclaimed from on high in four places.", - "Author": "Letter written by Terricus, acting commander of the Templars, speaking of Saladins capture of Jerusalem in 1187" - }, - { - "Text": "On this morning of May 17 rabi II, two days after the victory, the Sultan sought out the Templars and Hospitallers who had been captured and said \"I shall purify the land of these two impure races\".", - "Author": "Recorded by Imad-ad-Din, Secretary and Chancellor to Saladin 1187" - }, - { - "Text": "When the Saracens came to attack ... they threw Greek fire onto the barrier ... and the fire caught easily ... the Turks did not wait for the fire to burn itself out, but rushed upon the Templars among the scorching flames.", - "Author": "John of Joinville 11th February 1250" - }, - { - "Text": "Deus lo vult! - God wills it!", - "Author": "Pope Urban II, speaking about the first Crusade" - }, - { - "Text": "And you should know that there was at least an acre of land behind the Templars, which was so covered with arrows fired by the Saracens, that none of the ground could be seen.", - "Author": "John of Joinville 11th February 1250" - }, - { - "Text": "And by their arrogant and uncontrolled behavior, they had fired the anger of Almighty God as well against them.", - "Author": "William of Newburgh On the failure of the 2nd Crusade" - }, - { - "Text": "If you do not leave this pasturage, Saladin will come and attack you here. And if you retreat from this attack the shame and reproach will be very great.", - "Author": "Gerard of Ridefort, letter written to King Guy" - }, - { - "Text": "The strength of God will enable us, a small but faithful band, to overcome the multitude of the faithless.", - "Author": "Robert Guiscard" - }, - { - "Text": "Let anyone who has zeal for God come with me! Let us fight for our brothers! Let Heaven's will be done!", - "Author": "Conrad III, ruler of the Holy Roman Empire" - }, - { - "Text": "God has aroused the spirit of kings and princes to root up from the earth the enemies of the Christian name.", - "Author": "Bernard of Clairvaux" - }, - { - "Text": "Whoever devotedly undertakes and performs this most holy journey...shall have the enjoyment of eternal reward from the repayer of all men.", - "Author": "Pope Eugenius III" - }, - { - "Text": "Surrender before you all die by the sword, for I do not wish you to perish.", - "Author": "Imad ad-Din Zanghi" - }, - { - "Text": "Come on soldiers! Guardians and agents of the supreme law! Here is a sacrifice of dogs ready for your swords!", - "Author": "Il-Ghazi" - }, - { - "Text": "Put an end to so great an evil and arrive at a peace settlement whatever the outcome, and whatever the conditions.", - "Author": "William of Tyre" - }, - { - "Text": "Take up the weapons of the glorious army for the salvation of many thousands.", - "Author": "Adela, wife of Stephen of Blois" - }, - { - "Text": "If they wish to fight today, let them come like men.", - "Author": "Bohemund" - }, - { - "Text": "They assembled from all sides, one after another, with arms and horses and all the panoply of war...", - "Author": "Anna Comnenus, The Alexiad" - }, - { - "Text": "Therefore gird yourselves manfully and take up joyful arms for the name of Christ.", - "Author": "Bernard of Clairvaux" - }, - { - "Text": "Set out on pilgrimage and triumph gloriously over the infidels in the East.", - "Author": "Orderic Vitali" - }, - { - "Text": "hospitale sancte Marie Theutonicorum Jerosolimitanum - the Hospital of St. Mary of the Germans of Jerusalem", - "Author": "The Teutonic Order" - }, - { - "Text": "The forces of the Polish king were so numerous that there is no number high enough in the human language.", - "Author": "Prussian chronicles" - }, - { - "Text": "And they went against the godless Lithuanians, and thus for our sins they were defeated by the godless pagans, and only one man in ten came back to his home.", - "Author": "The Chronicle of Novgorod, 1237 on the Nemtsii" - }, - { - "Text": "The two armies met, and there was terrible carnage, and the crash of spears and their breaking and the clash of swords smiting as they moved over the frozen sea, and you could not see the ice, it was covered with blood.", - "Author": "Life of Alexandre Nevskii, The Battle of Lake Peipus, 1242" - }, - { - "Text": "With God's help he vanquished them, and the enemy forces turned and fled. But they [Alexandre's army] smote and pursued as if from the air; there was no place to which they [the Crusaders] could flee.", - "Author": "Life of Alexandre Nevskii, The Battle of Lake Peipus, 1242" - }, - { - "Text": "The brothers [Teutonic Knights] fought well enough, but they were nevertheless cut down. Some of those from Dorpat escaped from the battle, and it was their salvation that they had been forced to flee.", - "Author": "Livonian Rhymed Chronicle, The Battle of Lake Peipus, 1242" - }, - { - "Text": "throughout the battle galloped amidst the banners, replacing the exhausted and the fallen by new forces, he watched with utmost alertness as fortunes swayed between the combatants", - "Author": "Dlugosz on Grand Duke Alexander Vytautas, The Battle of Tannenburg, 1410" - }, - { - "Text": "A hundred picked men to plunder and harass the pagans... entering four villages that were not warned of their coming and putting to the sword whoever they find beginning their nights' sleep.", - "Author": "The actions of a Teutonic raider, 1372" - }, - { - "Text": "Hochmeister (Grand Master) head of the Order, elected for life by a General Conclave.", - "Author": "Ranks of the Teutonic Order" - }, - { - "Text": "Grosskomtur (Grand Commander) responsible for much of the administrative side of the Order.", - "Author": "Ranks of the Teutonic Order" - }, - { - "Text": "OrdenMarschall (Order Marshal) also known as the Grand Marshal. Usually resides at the castle of Konigsberg and is responsible for all military operations on the Lithuanian borders.", - "Author": "Ranks of the Teutonic Order" - }, - { - "Text": "Gross Hospittler (Hospitaller) responsible for organising and running the hospices and alm-houses of the Order.", - "Author": "Ranks of the Teutonic Order" - }, - { - "Text": "GrossTressler (Grand Treasurer) resides with the Hochmeister and is responsible for the 'state' treasury of the Order and almost all of their finances.", - "Author": "Ranks of the Teutonic Order" - }, - { - "Text": "OberstTrappier (Quartermaster) governs areas regarded as pacified.", - "Author": "Ranks of the Teutonic Order" - }, - { - "Text": "Landmeister (provincial Master) subordinate to the OrdenMarschall, the Landmeister is responsible for the administration and military operations of his Province.", - "Author": "Ranks of the Teutonic Order" - }, - { - "Text": "Komturei (the commandery) are the basic Order organisational unit. Each Komturei controls a district and Castle.", - "Author": "Ranks of the Teutonic Order" - }, - { - "Text": "He would water his horses in the Rhine", - "Author": "Boast attributed to Grand Duke Vytautus of Lithuania" - }, - { - "Text": "Spurning honesty and God, went against the Christians to destroy the lands of Prussia.", - "Author": "Posilge, the chronicler of the Teutonic Order on Bohemian mercenaries" - }, - { - "Text": "The Grand Duke instilled so much terror in all the knights that they shook like leaves before him.", - "Author": "Dlugosz on Grand Duke Alexander Vytautas" - }, - { - "Text": "At this very moment both armies, giving their war cries, met right in the middle of the valley...", - "Author": "Dlugosz on the battle of Tannenberg" - }, - { - "Text": "But you may rest assured that your childrens children will bewail your deeds.", - "Author": "Heinrich Reuss von Plauen 32nd Grand master of the Teutonic order" - } -] \ No newline at end of file diff --git a/NadekoBot/bin/Debug/libsodium.dll b/NadekoBot/bin/Debug/libsodium.dll deleted file mode 100644 index a9ab5078..00000000 Binary files a/NadekoBot/bin/Debug/libsodium.dll and /dev/null differ diff --git a/NadekoBot/bin/Debug/opus.dll b/NadekoBot/bin/Debug/opus.dll deleted file mode 100644 index a9eec802..00000000 Binary files a/NadekoBot/bin/Debug/opus.dll and /dev/null differ diff --git a/NadekoBot/packages.config b/NadekoBot/packages.config deleted file mode 100644 index 9a7951bf..00000000 --- a/NadekoBot/packages.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/NadekoBot/resources/images/cards/Thumbs.db b/NadekoBot/resources/images/cards/Thumbs.db deleted file mode 100644 index 13a0bb6c..00000000 Binary files a/NadekoBot/resources/images/cards/Thumbs.db and /dev/null differ diff --git a/NadekoBot/resources/images/rip/rip.png b/NadekoBot/resources/images/rip/rip.png deleted file mode 100644 index 6225b171..00000000 Binary files a/NadekoBot/resources/images/rip/rip.png and /dev/null differ diff --git a/NadekoBot/resources/images/rose_overlay.png b/NadekoBot/resources/images/rose_overlay.png deleted file mode 100644 index 767dcd1a..00000000 Binary files a/NadekoBot/resources/images/rose_overlay.png and /dev/null differ diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 00000000..d40268d3 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + diff --git a/README.md b/README.md index 68c18bbe..bb6a7dff 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ ![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true) [![Discord](https://discordapp.com/api/guilds/117523346618318850/widget.png)](https://discord.gg/0ehQwTK2RBjAxzEY) -[![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/1.0/?badge=latest) # NadekoBot +[![nadeko1](https://cdn.discordapp.com/attachments/155726317222887425/252095170676391936/A1.jpg)](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) +[![nadeko2](https://cdn.discordapp.com/attachments/155726317222887425/252095207514832896/A2.jpg)](http://nadekobot.readthedocs.io/en/1.0/Commands%20List/) + +##For Update, Help and Guidlines + +`Follow me on twitter for updates. | Join my Discord server if you need help. | Read the Docs for hosting guides.` + +[![twitter](https://cdn.discordapp.com/attachments/155726317222887425/252192520094613504/twiter_banner.JPG)](https://twitter.com/TheNadekoBot) [![discord](https://cdn.discordapp.com/attachments/155726317222887425/252192415673221122/discord_banner.JPG)](https://discord.gg/0ehQwTK2RBjAxzEY) [![Wiki](https://cdn.discordapp.com/attachments/155726317222887425/252192472849973250/read_the_docs_banner.JPG)](http://nadekobot.readthedocs.io/en/1.0/) -## [Click here to invite Nadeko to your Discord server](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) -## [Click here for a list of commands](http://nadekobot.readthedocs.io/en/latest/Commands%20List/) -## Instructions, FAQ, other info ---> [Documentation](http://nadekobot.readthedocs.io/en/latest) -You might want to join my discord server where i can provide help etc. https://discord.gg/0ehQwTK2RBjAxzEY diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..1b709532 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,11 @@ +version: 1.0.{build} +before_build: +- cmd: >- + git submodule update --init --recursive + + dotnet restore + + cd src/NadekoBot/ +build_script: +- cmd: >- + dotnet build \ No newline at end of file diff --git a/commandlist.md b/commandlist.md deleted file mode 100644 index 4d09a425..00000000 --- a/commandlist.md +++ /dev/null @@ -1,365 +0,0 @@ -######For more information and how to setup your own NadekoBot, go to: -######You can donate on patreon: -######or paypal: `nadekodiscordbot@gmail.com` - -#NadekoBot List Of Commands -### Help -Command and aliases | Description | Usage -----------------|--------------|------- -`-h`, `-help`, `@BotName help`, `@BotName h`, `~h` | Either shows a help for a single command, or PMs you help link if no arguments are specified. | `-h !m q` or just `-h` -`-hgit` | Generates the commandlist.md file. **Bot Owner Only!** | `-hgit` -`-readme`, `-guide` | Sends a readme and a guide links to the channel. | `-readme` or `-guide` -`-donate`, `~donate` | Instructions for helping the project! | `-donate` or `~donate` -`-modules`, `.modules` | List all bot modules. | `-modules` or `.modules` -`-commands`, `.commands` | List all of the bot's commands from a certain module. | `-commands` or `.commands` - -### Administration -Command and aliases | Description | Usage -----------------|--------------|------- -`.grdel` | Toggles automatic deletion of greet and bye messages. **Needs Manage Server Permissions.**| `.grdel` -`.greet` | Toggles anouncements on the current channel when someone joins the server. **Needs Manage Server Permissions.**| `.greet` -`.greetmsg` | Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. **Needs Manage Server Permissions.**| `.greetmsg Welcome to the server, %user%.` -`.bye` | Toggles anouncements on the current channel when someone leaves the server. | `.bye` -`.byemsg` | Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. **Needs Manage Server Permissions.**| `.byemsg %user% has left the server.` -`.byepm` | Toggles whether the good bye messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `.byepm` -`.greetpm` | Toggles whether the greet messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `.greetpm` -`.spmom` | Toggles whether mentions of other offline users on your server will send a pm to them. **Needs Manage Server Permissions.**| `.spmom` -`.logserver` | Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!** | `.logserver` -`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Bot Owner Only!**| `.logignore` -`.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. **Needs Manage Server Permissions.**| `.userpresence` -`.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. **Needs Manage Server Permissions.**| `.voicerpresence` -`.repeatinvoke`, `.repinv` | Immediately shows the repeat message and restarts the timer. **Needs Manage Messages Permissions.**| `.repinv` -`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. **Needs Manage Messages Permissions.** | `.repeat 5 Hello there` -`.rotateplaying`, `.ropl` | Toggles rotation of playing status of the dynamic strings you specified earlier. **Bot Owner Only!** | `.ropl` -`.addplaying`, `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %trivia% **Bot Owner Only!**| `.adpl` -`.listplaying`, `.lipl` | Lists all playing statuses with their corresponding number. **Bot Owner Only!**| `.lipl` -`.removeplaying`, `.repl`, `.rmpl` | Removes a playing string on a given number. **Bot Owner Only!**| `.rmpl` -`.slowmode` | Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. **Needs Manage Messages Permissions.**| `.slowmode` -`.cleanv+t`, `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk. -Needs Manage Roles and Manage Channels Permissions.** | `.cleanv+t` -`.voice+text`, `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. **Needs Manage Roles and Manage Channels Permissions.**| `.voice+text` -`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. **Bot Owner Only.** | `.scsc` -`.jcsc` | Joins current channel to an instance of cross server channel using the token. **Needs Manage Server Permissions.**| `.jcsc` -`.lcsc` | Leaves Cross server channel instance from this channel. **Needs Manage Server Permissions.**| `.lcsc` -`.asar` | Adds a role, or list of roles separated by whitespace(use quotations for multiword roles) to the list of self-assignable roles. **Needs Manage Roles Permissions.**| `.asar Gamer` -`.rsar` | Removes a specified role from the list of self-assignable roles. | `.rsar` -`.lsar` | Lists all self-assignable roles. | `.lsar` -`.togglexclsar`, `.tesar` | toggle whether the self-assigned roles should be exclusive | `.tesar` -`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | `.iam Gamer` -`.iamnot`, `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | `.iamn Gamer` -`.addcustreact`, `.acr` | Add a custom reaction. Guide here: **Bot Owner Only!** | `.acr "hello" I love saying hello to %user%` -`.listcustreact`, `.lcr` | Lists custom reactions (paginated with 30 commands per page). Use 'all' instead of page number to get all custom reactions DM-ed to you. | `.lcr 1` -`.showcustreact`, `.scr` | Shows all possible responses from a single custom reaction. | `.scr %mention% bb` -`.editcustreact`, `.ecr` | Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `.ecr "%mention% disguise" 2 Test 123` -`.delcustreact`, `.dcr` | Deletes a custom reaction with given name (and index). **Bot Owner Only.**| `.dcr index` -`.autoassignrole`, `.aar` | Automaticaly assigns a specified role to every user who joins the server. **Needs Manage Roles Permissions.** | `.aar` to disable, `.aar Role Name` to enable -`.leave` | Makes Nadeko leave the server. Either name or id required. **Bot Owner Only!**| `.leave 123123123331` -`.listincidents`, `.lin` | List all UNREAD incidents and flags them as read. **Needs Manage Server Permissions.**| `.lin` -`.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. **Needs Manage Server Permissions.**| `.lain` -`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. **Server Manager Only.** | `.delmsgoncmd` -`.restart` | Restarts the bot. Might not work. **Bot Owner Only** | `.restart` -`.setrole`, `.sr` | Sets a role for a given user. **Needs Manage Roles Permissions.**| `.sr @User Guest` -`.removerole`, `.rr` | Removes a role from a given user. **Needs Manage Roles Permissions.**| `.rr @User Admin` -`.renamerole`, `.renr` | Renames a role. Roles you are renaming must be lower than bot's highest role. **Manage Roles Permissions.** | `.renr "First role" SecondRole` -`.removeallroles`, `.rar` | Removes all roles from a mentioned user. **Needs Manage Roles Permissions.**| `.rar @User` -`.createrole`, `.cr` | Creates a role with a given name. **Needs Manage Roles Permissions.**| `.cr Awesome Role` -`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. **Needs Manage Roles Permissions.** | `.rc Admin 255 200 100` or `.rc Admin ffba55` -`.ban`, `.b` | Bans a user by id or name with an optional message. **Needs Ban Permissions.**| `.b "@some Guy" Your behaviour is toxic.` -`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. **Needs Ban Permissions.**| `.sb "@some Guy" Your behaviour is toxic.` -`.kick`, `.k` | Kicks a mentioned user. **Needs Kick Permissions.**| `.k "@some Guy" Your behaviour is toxic.` -`.mute` | Mutes mentioned user or users. **Needs Mute Permissions.**| `.mute "@Someguy"` or `.mute "@Someguy" "@Someguy"` -`.unmute` | Unmutes mentioned user or users. **Needs Mute Permissions.**| `.unmute "@Someguy"` or `.unmute "@Someguy" "@Someguy"` -`.deafen`, `.deaf` | Deafens mentioned user or users. **Needs Deafen Permissions.**| `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"` -`.undeafen`, `.undef` | Undeafens mentioned user or users. **Needs Deafen Permissions.** | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"` -`.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. **Needs Manage Channel Permissions.**| `.dvch VoiceChannelName` -`.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. **Needs Manage Channel Permissions.** | `.cvch VoiceChannelName` -`.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. **Needs Manage Channel Permissions.** | `.dtch TextChannelName` -`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. **Needs Manage Channel Permissions.** | `.ctch TextChannelName` -`.settopic`, `.st` | Sets a topic on the current channel. **Needs Manage Channel Permissions.** | `.st My new topic` -`.setchanlname`, `.schn` | Changed the name of the current channel. **Needs Manage Channel Permissions.**| `.schn NewName` -`.heap` | Shows allocated memory - **Bot Owner Only!** | `.heap` -`.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` -`.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` -`.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!` -`.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` -`.donators` | List of lovely people who donated to keep this project alive. | `.donators` -`.donadd` | Add a donator to the database. **Kwoth Only** | `.donadd Donate Amount` -`.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** | `.savechat 150` - -### Utility -Command and aliases | Description | Usage -----------------|--------------|------- -`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!` -`.remindmsg` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!** | `.remindmsg do something else` -`.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` -`.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 Overwatch` -`.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` -`.stats` | Shows some basic stats for Nadeko. | `.stats` -`.dysyd` | Shows some basic stats for Nadeko. | `.dysyd` -`.userid`, `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"` -`.channelid`, `.cid` | Shows current channel ID. | `.cid` -`.serverid`, `.sid` | Shows current server ID. | `.sid` -`.roles` | List all roles on this server or a single user if specified. | `.roles` -`.channeltopic`, `.ct` | Sends current channel's topic as a message. | `.ct` - -### Permissions -Command and aliases | Description | Usage -----------------|--------------|------- -`;chnlfilterinv`, `;cfi` | Enables or disables automatic deleting of invites on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | `;cfi enable #general-chat` -`;srvrfilterinv`, `;sfi` | Enables or disables automatic deleting of invites on the server. | `;sfi disable` -`;chnlfilterwords`, `;cfw` | Enables or disables automatic deleting of messages containing banned words on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | `;cfw enable #general-chat` -`;addfilterword`, `;afw` | Adds a new word to the list of filtered words | `;afw poop` -`;rmvfilterword`, `;rfw` | Removes the word from the list of filtered words | `;rw poop` -`;lstfilterwords`, `;lfw` | Shows a list of filtered words | `;lfw` -`;srvrfilterwords`, `;sfw` | Enables or disables automatic deleting of messages containing forbidden words on the server. | `;sfw disable` -`;permrole`, `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr 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` -`;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` -`;srvrperms`, `;sp` | Shows banned permissions for this server. | `;sp` -`;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` -`;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` -`;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` -`;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` -`;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` -`;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]` -`;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` -`;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` -`;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]` -`;uubl` | Unblacklists a mentioned user. | `;uubl [user_mention]` -`;cbl` | Blacklists a mentioned channel (#general for example). | `;cbl #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]` -`;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. | `;acmdcds` - -### Conversations -Command and aliases | Description | Usage -----------------|--------------|------- -`..` | 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` -`..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 die` | Works only for the owner. Shuts the bot down. | `@NadekoBot die` -`@BotName do you love me` | Replies with positive answer only to the bot owner. | `@NadekoBot do you love me` -`@BotName how are you`, `@BotName how are you?` | Replies positive only if bot owner is online. | `@NadekoBot how are you` -`@BotName fire` | Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire. | `@NadekoBot fire [x]` -`@BotName dump` | Dumps all of the invites it can to dump.txt.** Owner Only.** | `@NadekoBot dump` -`@BotName ab` | Try to get 'abalabahaha'| `@NadekoBot ab` - -### Gambling -Command and aliases | Description | Usage -----------------|--------------|------- -`$draw` | Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | `$draw [x]` -`$shuffle`, `$sh` | Reshuffles all cards back into the deck.|`$shuffle` -`$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3` -`$betflip`, `$bf` | Bet to guess will the result be heads or tails. Guessing award you double flowers you've bet. | `$bf 5 heads` or `$bf 3 t` -`$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` -`$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` -`$race` | Starts a new animal race. | `$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` -`$give` | Give someone a certain amount of NadekoFlowers|`$give 1 "@SomeGuy"` -`$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 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` -`$leaderboard`, `$lb` | Displays bot currency leaderboard | `$lb` - -### Games -Command and aliases | Description | Usage -----------------|--------------|------- -`>t` | Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `>t nohint` or `>t 5 nohint` -`>tl` | Shows a current trivia leaderboard. | `>tl` -`>tq` | Quits current trivia after current question. | `>tq` -`>typestart` | Starts a typing contest. | `>typestart` -`>typestop` | Stops a typing contest on the current channel. | `>typestop` -`>typeadd` | Adds a new article to the typing contest. Owner only. | `>typeadd wordswords` -`>poll` | Creates a poll, only person who has manage server permission can do it. | `>poll Question?;Answer1;Answ 2;A_3` -`>pollend` | Stops active poll on this server and prints the results in this channel. | `>pollend` -`>pick` | Picks a flower planted in this channel. | `>pick` -`>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost) | `>plant` -`>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` -`>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more` -`>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` -`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` - -### Music -Command and aliases | Description | Usage -----------------|--------------|------- -`!!next`, `!!n`, `!!skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!!n` -`!!stop`, `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s` -`!!destroy`, `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d` -`!!pause`, `!!p` | Pauses or Unpauses the song. | `!!p` -`!!queue`, `!!q`, `!!yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!!q Dream Of Venice` -`!!soundcloudqueue`, `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**. | `!!sq Dream Of Venice` -`!!listqueue`, `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2` -`!!nowplaying`, `!!np` | Shows the song currently playing. | `!!np` -`!!volume`, `!!vol` | Sets the music volume 0-100% | `!!vol 50` -`!!defvol`, `!!dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!!dv 80` -`!!mute`, `!!min` | Sets the music volume to 0% | `!!min` -`!!max` | Sets the music volume to 100%. | `!!max` -`!!half` | Sets the music volume to 50%. | `!!half` -`!!shuffle`, `!!sh` | Shuffles the current playlist. | `!!sh` -`!!playlist`, `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name` -`!!soundcloudpl`, `!!scpl` | Queue a soundcloud playlist using a link. | `!!scpl https://soundcloud.com/saratology/sets/symphony` -`!!localplaylst`, `!!lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!!lopl C:/music/classical` -`!!radio`, `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `!!ra radio link here` -`!!local`, `!!lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!!lo C:/music/mysong.mp3` -`!!move`, `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv` -`!!remove`, `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5` -`!!movesong`, `!!ms` | Moves a song from one position to another. | `!! ms` 5>3 -`!!setmaxqueue`, `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq` 50 or `!!smq` -`!!cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!!cleanup` -`!!reptcursong`, `!!rcs` | Toggles repeat of current song. | `!!rcs` -`!!rpeatplaylst`, `!!rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!!rpl` -`!!save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!!save classical1` -`!!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` -`!!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 30` -`!!getlink`, `!!gl` | Shows a link to the song in the queue by index, or the currently playing song by default. | `!!gl` -`!!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) | `!!ap` - -### Searches -Command and aliases | Description | Usage -----------------|--------------|------- -`~lolchamp` | Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. | `~lolchamp Riven` or `~lolchamp Annie sup` -`~lolban` | Shows top 6 banned champions ordered by ban rate. Ban these champions and you will be Plat 5 in no time. | `~lolban` -`~hitbox`, `~hb` | Notifies this channel when a certain user starts streaming. | `~hitbox SomeStreamer` -`~twitch`, `~tw` | Notifies this channel when a certain user starts streaming. | `~twitch SomeStreamer` -`~beam`, `~bm` | Notifies this channel when a certain user starts streaming. | `~beam SomeStreamer` -`~checkhitbox`, `~chhb` | Checks if a certain user is streaming on the hitbox platform. | `~chhb SomeStreamer` -`~checktwitch`, `~chtw` | Checks if a certain user is streaming on the twitch platform. | `~chtw SomeStreamer` -`~checkbeam`, `~chbm` | Checks if a certain user is streaming on the beam platform. | `~chbm SomeStreamer` -`~removestream`, `~rms` | Removes notifications of a certain streamer on this channel. | `~rms SomeGuy` -`~liststreams`, `~ls` | Lists all streams you are following on this server. | `~ls` -`~convert` | Convert quantities from>to. | `~convert m>km 1000` -`~convertlist` | List of the convertable dimensions and currencies. -`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke` -`~calculate`, `~calc` | Evaluate a mathematical expression. | `~calc 1+1` -`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko` -`~osu b` | Shows information about an osu beatmap. | `~osu b` https://osu.ppy.sh/s/127712 -`~osu top5` | Displays a user's top 5 plays. | `~osu top5 Name` -`~pokemon`, `~poke` | Searches for a pokemon. | `~poke Sylveon` -`~pokemonability`, `~pokeab` | Searches for a pokemon ability. | `~pokeab "water gun"` -`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist` -`~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` -`~yt` | Searches youtubes and shows the first result | `~yt query` -`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. | `~aq aquarion evol` -`~imdb` | Queries imdb for movies or series, show first result. | `~imdb Batman vs Superman` -`~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. | `~mq Shingeki no kyojin` -`~randomcat`, `~meow` | Shows a random cat image. | `~meow` -`~randomdog`, `~woof` | Shows a random dog image. | `~woof` -`~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | `~i cute kitten` -`~ir` | Pulls a random image using a search parameter. | `~ir cute kitten` -`~lmgtfy` | Google something for an idiot. | `~lmgtfy query` -`~google`, `~g` | Get a google search link for some terms. | `~google query` -`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera` -`~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple` -`~#` | Searches Tagdef.com for a hashtag. | `~# ff` -`~quote` | Shows a random quote. | `~quote` -`~catfact` | Shows a random catfact from | `~catfact` -`~yomama`, `~ym` | Shows a random joke from | `~ym` -`~randjoke`, `~rj` | Shows a random joke from | `~rj` -`~chucknorris`, `~cn` | Shows a random chucknorris joke from | `~cn` -`~magicitem`, `~mi` | Shows a random magicitem from | `~mi` -`~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. | `~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` -`~wiki` | Gives you back a wikipedia link | `~wiki query` -`~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` -`~videocall` | Creates a private 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 "@SomeGuy"` - -### NSFW -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` -`~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` -`~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` -`~cp` | We all know where this will lead you to. | `~cp` -`~boobs` | Real adult content. | `~boobs` -`~butts`, `~ass`, `~butt` | Real adult content. | `~butts` or `~ass` - -### ClashOfClans -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` -`,startwar`, `,sw` | Starts a war with a given number. | `,sw 15` -`,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]` -`,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]` -`,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 whose place to unclaim | `,uc [war_number] [optional_other_name]` -`,endwar`, `,ew` | Ends the war with a given index. | `,ew [war_number]` - -### Pokegame -Command and aliases | Description | Usage -----------------|--------------|------- -`>attack` | Attacks a target with the given move. Use `>movelist` to see a list of moves your type can use. | `>attack "vine whip" @someguy` -`>movelist`, `>ml` | Lists the moves you are able to use | `>ml` -`>heal` | Heals someone. Revives those who fainted. Costs a NadekoFlower | `>heal @someone` -`>type` | Get the poketype of the target. | `>type @someone` -`>settype` | Set your poketype. Costs a NadekoFlower. | `>settype fire` - -### Translator -Command and aliases | Description | Usage -----------------|--------------|------- -`~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` or `~translangs language` - -### Customreactions -Command and aliases | Description | Usage -----------------|--------------|------- -`\o\` | Custom reaction. | `\o\` -`/o/` | Custom reaction. | `/o/` -`moveto` | Custom reaction. | `moveto` -`comeatmebro` | Custom reaction. | `comeatmebro` -`e` | Custom reaction. | `e` -`@BotName insult`, `<@!116275390695079945> insult` | Custom reaction. | `%mention% insult` -`@BotName praise`, `<@!116275390695079945> praise` | Custom reaction. | `%mention% praise` -`@BotName pat`, `<@!116275390695079945> pat` | Custom reaction. | `%mention% pat` -`@BotName cry`, `<@!116275390695079945> cry` | Custom reaction. | `%mention% cry` -`@BotName are you real?`, `<@!116275390695079945> are you real?` | Custom reaction. | `%mention% are you real?` -`@BotName are you there?`, `<@!116275390695079945> are you there?` | Custom reaction. | `%mention% are you there?` -`@BotName draw`, `<@!116275390695079945> draw` | Custom reaction. | `%mention% draw` -`@BotName bb`, `<@!116275390695079945> bb` | Custom reaction. | `%mention% bb` -`@BotName call`, `<@!116275390695079945> call` | Custom reaction. | `%mention% call` -`@BotName disguise`, `<@!116275390695079945> disguise` | Custom reaction. | `%mention% disguise` -`@BotName inv`, `<@!116275390695079945> inv` | Custom reaction. | `%mention% inv` -`@BotName threaten`, `<@!116275390695079945> threaten` | Custom reaction. | `%mention% threaten` -`@BotName archer`, `<@!116275390695079945> archer` | Custom reaction. | `%mention% archer` - -### Trello -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. **Bot Owner Only!**| `trello bind [board_id]` -`trello unbind` | Unbinds a bot from the channel and board. **Bot Owner Only!**| `trello unbind` -`trello lists`, `trello list` | Lists all lists, yo ;) **Bot Owner Only!**| `trello list` -`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. **Bot Owner Only!**| `trello cards index` diff --git a/discord.net b/discord.net index f7655320..48b66d55 160000 --- a/discord.net +++ b/discord.net @@ -1 +1 @@ -Subproject commit f7655320bc2b90ddb8bc9d17ae999aa76e9f5f38 +Subproject commit 48b66d55f3f26f60fc001123ff6d7afeade3a51d diff --git a/docs/Commands List.md b/docs/Commands List.md index aec52aa2..3b38e04f 100644 --- a/docs/Commands List.md +++ b/docs/Commands List.md @@ -1,360 +1,360 @@ +You can support the project on patreon: or paypal: -### Help -Command and aliases | Description | Usage -----------------|--------------|------- -`-h`, `-help`, `@BotName help`, `@BotName h`, `~h` | Either shows a help for a single command, or PMs you help link if no arguments are specified. | `-h !m q` or just `-h` -`-hgit` | Generates the commandlist.md file. **Bot Owner Only!** | `-hgit` -`-readme`, `-guide` | Sends a readme and a guide links to the channel. | `-readme` or `-guide` -`-donate`, `~donate` | Instructions for helping the project! | `-donate` or `~donate` -`-modules`, `.modules` | List all bot modules. | `-modules` or `.modules` -`-commands`, `.commands` | List all of the bot's commands from a certain module. | `-commands` or `.commands` +##Table Of Contents +- [Help](#help) +- [Administration](#administration) +- [ClashOfClans](#clashofclans) +- [CustomReactions](#customreactions) +- [Gambling](#gambling) +- [Games](#games) +- [Music](#music) +- [NSFW](#nsfw) +- [Permissions](#permissions) +- [Searches](#searches) +- [Utility](#utility) -### Administration -Command and aliases | Description | Usage -----------------|--------------|------- -`.grdel` | Toggles automatic deletion of greet and bye messages. **Needs Manage Server Permissions.**| `.grdel` -`.greet` | Toggles anouncements on the current channel when someone joins the server. **Needs Manage Server Permissions.**| `.greet` -`.greetmsg` | Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. **Needs Manage Server Permissions.**| `.greetmsg Welcome, %user%.` -`.bye` | Toggles anouncements on the current channel when someone leaves the server. | `.bye` -`.byemsg` | Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. **Needs Manage Server Permissions.**| `.byemsg %user% has left.` -`.byepm` | Toggles whether the good bye messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `.byepm` -`.greetpm` | Toggles whether the greet messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `.greetpm` -`.spmom` | Toggles whether mentions of other offline users on your server will send a pm to them. **Needs Manage Server Permissions.**| `.spmom` -`.logserver` | Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!** | `.logserver` -`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Bot Owner Only!**| `.logignore` -`.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. **Needs Manage Server Permissions.**| `.userpresence` -`.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. **Needs Manage Server Permissions.**| `.voicerpresence` -`.repeatinvoke`, `.repinv` | Immediately shows the repeat message and restarts the timer. **Needs Manage Messages Permissions.**| `.repinv` -`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. **Needs Manage Messages Permissions.** |`.repeat 5 Hello there` -`.rotateplaying`, `.ropl` | Toggles rotation of playing status of the dynamic strings you specified earlier. **Bot Owner Only!** | `.ropl` -`.addplaying`, `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %trivia% **Bot Owner Only!**| `.adpl` -`.listplaying`, `.lipl` | Lists all playing statuses with their corresponding number. **Bot Owner Only!**| `.lipl` -`.removeplaying`, `.repl`, `.rmpl` | Removes a playing string on a given number. **Bot Owner Only!**| `.rmpl` -`.slowmode` | Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. **Needs Manage Messages Permissions.**| `.slowmode` -`.cleanv+t`, `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk. Needs Manage Roles and Manage Channels Permissions.** | `.cleanv+t` -`.voice+text`, `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. **Needs Manage Roles and Manage Channels Permissions.**| `.voice+text` -`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. **Bot Owner Only.** | `.scsc` -`.jcsc` | Joins current channel to an instance of cross server channel using the token. **Needs Manage Server Permissions.**| `.jcsc` -`.lcsc` | Leaves Cross server channel instance from this channel. **Needs Manage Server Permissions.**| `.lcsc` -`.asar` | Adds a role, or list of roles separated by whitespace(use quotations for multiword roles) to the list of self-assignable roles. **Needs Manage Roles Permissions.**| `.asar Gamer` -`.rsar` | Removes a specified role from the list of self-assignable roles. | `.rsar` -`.lsar` | Lists all self-assignable roles. | `.lsar` -`.togglexclsar`, `.tesar` | toggle whether the self-assigned roles should be exclusive | `.tesar` -`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | `.iam Gamer` -`.iamnot`, `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | `.iamn Gamer` -`.addcustreact`, `.acr` | Add a custom reaction. Guide here: **Bot Owner Only!** | `.acr "hello" Hi there %user%` -`.listcustreact`, `.lcr` | Lists custom reactions (paginated with 30 commands per page). Use 'all' instead of page number to get all custom reactions DM-ed to you. |`.lcr 1` -`.showcustreact`, `.scr` | Shows all possible responses from a single custom reaction. |`.scr %mention% bb` -`.editcustreact`, `.ecr` | Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `.ecr "%mention% disguise" 2 Test 123` -`.delcustreact`, `.dcr` | Deletes a custom reaction with given name (and index). **Bot Owner Only.**| `.dcr index` -`.autoassignrole`, `.aar` | Automaticaly assigns a specified role to every user who joins the server. **Needs Manage Roles Permissions.** |`.aar` to disable, `.aar Role Name` to enable -`.leave` | Makes Nadeko leave the server. Either name or id required. **Bot Owner Only!**| `.leave 123123123331` -`.listincidents`, `.lin` | List all UNREAD incidents and flags them as read. **Needs Manage Server Permissions.**| `.lin` -`.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. **Needs Manage Server Permissions.**| `.lain` -`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. **Server Manager Only.** | `.delmsgoncmd` -`.restart` | Restarts the bot. Might not work. **Bot Owner Only** | `.restart` -`.setrole`, `.sr` | Sets a role for a given user. **Needs Manage Roles Permissions.**| `.sr @User Guest` -`.removerole`, `.rr` | Removes a role from a given user. **Needs Manage Roles Permissions.**| `.rr @User Admin` -`.renamerole`, `.renr` | Renames a role. Roles you are renaming must be lower than bot's highest role. **Manage Roles Permissions.** | `.renr "First role" SecondRole` -`.removeallroles`, `.rar` | Removes all roles from a mentioned user. **Needs Manage Roles Permissions.**| `.rar @User` -`.createrole`, `.cr` | Creates a role with a given name. **Needs Manage Roles Permissions.**| `.cr Awesome Role` -`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. **Needs Manage Roles Permissions.** | `.rc Admin 255 200 100` or `.rc Admin ffba55` -`.ban`, `.b` | Bans a user by id or name with an optional message. **Needs Ban Permissions.**| `.b "@some Guy" Your behaviour is toxic.` -`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. **Needs Ban Permissions.**| `.sb "@some Guy" Your behaviour is toxic.` -`.kick`, `.k` | Kicks a mentioned user. **Needs Kick Permissions.**| `.k "@some Guy" Your behaviour is toxic.` -`.mute` | Mutes mentioned user or users. **Needs Mute Permissions.**| `.mute "@Someguy"` or `.mute "@Someguy" "@Someguy"` -`.unmute` | Unmutes mentioned user or users. **Needs Mute Permissions.**| `.unmute "@Someguy"` or `.unmute "@Someguy" "@Someguy"` -`.deafen`, `.deaf` | Deafens mentioned user or users. **Needs Deafen Permissions.**| `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"` -`.undeafen`, `.undef` | Undeafens mentioned user or users. **Needs Deafen Permissions.** | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"` -`.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. **Needs Manage Channel Permissions.**| `.dvch VoiceChannelName` -`.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. **Needs Manage Channel Permissions.** | `.cvch VoiceChannelName` -`.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. **Needs Manage Channel Permissions.** | `.dtch TextChannelName` -`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. **Needs Manage Channel Permissions.** | `.ctch TextChannelName` -`.settopic`, `.st` | Sets a topic on the current channel. **Needs Manage Channel Permissions.** | `.st My new topic` -`.setchanlname`, `.schn` | Changed the name of the current channel. **Needs Manage Channel Permissions.**| `.schn NewName` -`.heap` | Shows allocated memory - **Bot Owner Only!** | `.heap` -`.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. **Needs Manage Messages Permissions**| `.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` -`.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 http://i.imgur.com/xTG3a1I.jpg` -`.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 sid|u:uid Hello user!` or `.send sid|c:cid Message to channel!` (cid = channel id, sid = server id) -`.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` -`.donators` | List of lovely people who donated to keep this project alive. | `.donators` -`.donadd` | Add a donator to the database. **Kwoth Only** | `.donadd Donate Amount` -`.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** | `.savechat 150` -### Utility -Command and aliases | Description | Usage -----------------|--------------|------- -`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!` -`.remindmsg` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner Only!** | `.remindmsg do something else` -`.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` -`.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 Overwatch` -`.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` -`.stats` | Shows some basic stats for Nadeko. | `.stats` -`.dysyd` | Shows some basic stats for Nadeko. | `.dysyd` -`.userid`, `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"` -`.channelid`, `.cid` | Shows current channel ID. | `.cid` -`.serverid`, `.sid` | Shows current server ID. | `.sid` -`.roles` | List all roles on this server or a single user if specified. | `.roles` -`.channeltopic`, `.ct` | Sends current channel's topic as a message. | `.ct` +### Administration +Command and aliases | Description | Usage +----------------|--------------|------- +`.resetperms` | Resets BOT's permissions module on this server to the default value. **Requires Administrator server permission.** | `.resetperms` +`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. **Requires Administrator server permission.** | `.delmsgoncmd` +`.setrole` `.sr` | Sets a role for a given user. **Requires ManageRoles server permission.** | `.sr @User Guest` +`.removerole` `.rr` | Removes a role from a given user. **Requires ManageRoles server permission.** | `.rr @User Admin` +`.renamerole` `.renr` | Renames a role. Roles you are renaming must be lower than bot's highest role. **Requires ManageRoles server permission.** | `.renr "First role" SecondRole` +`.removeallroles` `.rar` | Removes all roles from a mentioned user. **Requires ManageRoles server permission.** | `.rar @User` +`.createrole` `.cr` | Creates a role with a given name. **Requires ManageRoles server permission.** | `.cr Awesome Role` +`.rolecolor` `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. **Requires ManageRoles server permission.** | `.rc Admin 255 200 100` or `.rc Admin ffba55` +`.ban` `.b` | Bans a user by ID or name with an optional message. **Requires BanMembers server permission.** | `.b "@some Guy" Your behaviour is toxic.` +`.softban` `.sb` | Bans and then unbans a user by ID or name with an optional message. **Requires KickMembers server permission.** **Requires ManageMessages server permission.** | `.sb "@some Guy" Your behaviour is toxic.` +`.kick` `.k` | Kicks a mentioned user. **Requires KickMembers server permission.** | `.k "@some Guy" Your behaviour is toxic.` +`.setmuterole` | Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute. **Requires ManageRoles server permission.** | `.setmuterole Silenced` +`.mute` | Mutes a mentioned user both from speaking and chatting. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.mute @Someone` +`.unmute` | Unmutes a mentioned user previously muted with `.mute` command. **Requires ManageRoles server permission.** **Requires MuteMembers server permission.** | `.unmute @Someone` +`.chatmute` | Prevents a mentioned user from chatting in text channels. **Requires ManageRoles server permission.** | `.chatmute @Someone` +`.chatunmute` | Removes a mute role previously set on a mentioned user with `.chatmute` which prevented him from chatting in text channels. **Requires ManageRoles server permission.** | `.chatunmute @Someone` +`.voicemute` | Prevents a mentioned user from speaking in voice channels. **Requires MuteMembers server permission.** | `.voicemute @Someone` +`.voiceunmute` | Gives a previously voice-muted user a permission to speak. **Requires MuteMembers server permission.** | `.voiceunmute @Someguy` +`.deafen` `.deaf` | Deafens mentioned user or users. **Requires DeafenMembers server permission.** | `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"` +`.undeafen` `.undef` | Undeafens mentioned user or users. **Requires DeafenMembers server permission.** | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"` +`.delvoichanl` `.dvch` | Deletes a voice channel with a given name. **Requires ManageChannels server permission.** | `.dvch VoiceChannelName` +`.creatvoichanl` `.cvch` | Creates a new voice channel with a given name. **Requires ManageChannels server permission.** | `.cvch VoiceChannelName` +`.deltxtchanl` `.dtch` | Deletes a text channel with a given name. **Requires ManageChannels server permission.** | `.dtch TextChannelName` +`.creatxtchanl` `.ctch` | Creates a new text channel with a given name. **Requires ManageChannels server permission.** | `.ctch TextChannelName` +`.settopic` `.st` | Sets a topic on the current channel. **Requires ManageChannels server permission.** | `.st My new topic` +`.setchanlname` `.schn` | Changes the name of the current channel. **Requires ManageChannels server permission.** | `.schn NewName` +`.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. **Bot Owner only.** | `.die` +`.setname` `.newnm` | Gives the bot a new name. **Bot Owner only.** | `.newnm BotName` +`.setavatar` `.setav` | Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. **Bot Owner only.** | `.setav http://i.imgur.com/xTG3a1I.jpg` +`.setgame` | Sets the bots game. **Bot Owner only.** | `.setgame with snakes` +`.setstream` | Sets the bots stream. First argument is the twitch link, second argument is stream name. **Bot Owner only.** | `.setstream TWITCHLINK Hello` +`.send` | Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prepend channel id with `c:` and user id with `u:`. **Bot Owner only.** | `.send serverid|c:channelid message` or `.send serverid|u:userid message` +`.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.** | `.savechat 150` +`.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. **Requires MentionEveryone server permission.** | `.menro RoleName` +`.donators` | List of lovely people who donated to keep this project alive. | `.donators` +`.donadd` | Add a donator to the database. **Bot Owner only.** | `.donadd Donate Amount` +`.antiraid` | Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute) **Requires Administrator server permission.** | `.antiraid 5 20 Kick` +`.antispam` | Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban` +`.autoassignrole` `.aar` | Automaticaly assigns a specified role to every user who joins the server. **Requires ManageRoles server permission.** | `.aar` to disable, `.aar Role Name` to enable +`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. **Bot Owner only.** | `.scsc` +`.jcsc` | Joins current channel to an instance of cross server channel using the token. **Requires ManageServer server permission.** | `.jcsc TokenHere` +`.lcsc` | Leaves Cross server channel instance from this channel. **Requires ManageServer server permission.** | `.lcsc` +`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot Owner only.** | `.fwmsgs` +`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json **Bot Owner only.** | `.fwtoall` +`.logserver` | Logs server activity in this channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logserver` +`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Requires Administrator server permission.** **Bot Owner only.** | `.logignore` +`.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. **Requires Administrator server permission.** | `.userpresence` +`.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are currently in. **Requires Administrator server permission.** | `.voicepresence` +`.repeatinvoke` `.repinv` | Immediately shows the repeat message and restarts the timer. **Requires ManageMessages server permission.** | `.repinv` +`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. **Requires ManageMessages server permission.** | `.repeat 5 Hello there` +`.migratedata` | Migrate data from old bot configuration **Bot Owner only.** | `.migratedata` +`.rotateplaying` `.ropl` | Toggles rotation of playing status of the dynamic strings you previously specified. **Bot Owner only.** | `.ropl` +`.addplaying` `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued% **Bot Owner only.** | `.adpl` +`.listplaying` `.lipl` | Lists all playing statuses with their corresponding number. **Bot Owner only.** | `.lipl` +`.removeplaying` `.rmpl` `.repl` | Removes a playing string on a given number. **Bot Owner only.** | `.rmpl` +`.slowmode` | Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds. **Requires ManageMessages server permission.** | `.slowmode 1 5` or `.slowmode` +`.adsarm` | Toggles the automatic deletion of confirmations for .iam and .iamn commands. **Requires ManageMessages server permission.** | `.adsarm` +`.asar` | Adds a role to the list of self-assignable roles. **Requires ManageRoles server permission.** | `.asar Gamer` +`.rsar` | Removes a specified role from the list of self-assignable roles. **Requires ManageRoles server permission.** | `.rsar` +`.lsar` | Lists all self-assignable roles. | `.lsar` +`.togglexclsar` `.tesar` | Toggles whether the self-assigned roles are exclusive. (So that any person can have only one of the self assignable roles) **Requires ManageRoles server permission.** | `.tesar` +`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | `.iam Gamer` +`.iamnot` `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | `.iamn Gamer` +`.leave` | Makes Nadeko leave the server. Either name or id required. **Bot Owner only.** | `.leave 123123123331` +`.greetdel` `.grdel` | Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.greetdel 0` or `.greetdel 30` +`.greet` | Toggles anouncements on the current channel when someone joins the server. **Requires ManageServer server permission.** | `.greet` +`.greetmsg` | Sets a new join announcement message which will be shown in the server's channel. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. **Requires ManageServer server permission.** | `.greetmsg Welcome, %user%.` +`.greetdm` | Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled). **Requires ManageServer server permission.** | `.greetdm` +`.greetdmmsg` | Sets a new join announcement message which will be sent to the user who joined. Type %user% if you want to mention the new member. Using it with no message will show the current DM greet message. **Requires ManageServer server permission.** | `.greetdmmsg Welcome to the server, %user%`. +`.bye` | Toggles anouncements on the current channel when someone leaves the server. **Requires ManageServer server permission.** | `.bye` +`.byemsg` | Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. **Requires ManageServer server permission.** | `.byemsg %user% has left.` +`.byedel` | Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set 0 to disable automatic deletion. **Requires ManageServer server permission.** | `.byedel 0` or `.byedel 30` +`.voice+text` `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. **Requires ManageRoles server permission.** **Requires ManageChannels server permission.** | `.voice+text` +`.cleanvplust` `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk. **Requires ManageChannels server permission.** **Requires ManageRoles server permission.** | `.cleanv+t` -### Permissions -Command and aliases | Description | Usage -----------------|--------------|------- -`;chnlfilterinv`, `;cfi` | Enables or disables automatic deleting of invites on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | `;cfi enable #general-chat` -`;srvrfilterinv`, `;sfi` | Enables or disables automatic deleting of invites on the server. | `;sfi disable` -`;chnlfilterwords`, `;cfw` | Enables or disables automatic deleting of messages containing banned words on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | `;cfw enable #general-chat` -`;addfilterword`, `;afw` | Adds a new word to the list of filtered words | `;afw poop` -`;rmvfilterword`, `;rfw` | Removes the word from the list of filtered words | `;rw poop` -`;lstfilterwords`, `;lfw` | Shows a list of filtered words | `;lfw` -`;srvrfilterwords`, `;sfw` | Enables or disables automatic deleting of messages containing forbidden words on the server. | `;sfw disable` -`;permrole`, `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr 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` -`;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` -`;srvrperms`, `;sp` | Shows banned permissions for this server. | `;sp` -`;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` -`;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` -`;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` -`;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` -`;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` -`;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]` -`;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` -`;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` -`;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. **Bot Owner Only!**| `;ubl [user_mention]` -`;uubl` | Unblacklists a mentioned user. **Bot Owner Only!** | `;uubl [user_mention]` -`;cbl` | Blacklists a mentioned channel (#general for example). | `;cbl #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]` -`;cmdcooldown`, `;cmdcd` | Sets a cooldown per user for a command. Set 0 to clear. **Needs Manager Messages Permissions**| `;cmdcd "some cmd" 5` -`;allcmdcooldowns`, `;acmdcds` | Shows a list of all commands and their respective cooldowns. | `;acmdcds` +###### [Back to TOC](#table-of-contents) -### Conversations -Command and aliases | Description | Usage -----------------|--------------|------- -`..` | 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` -`..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 die` | Works only for the owner. Shuts the bot down. | `@NadekoBot die` -`@BotName do you love me` | Replies with positive answer only to the bot owner. | `@NadekoBot do you love me` -`@BotName how are you`, `@BotName how are you?` | Replies positive only if bot owner is online. | `@NadekoBot how are you` -`@BotName fire` | Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire. | `@NadekoBot fire [x]` -`@BotName dump` | Dumps all of the invites it can to dump.txt.** Owner Only.** | `@NadekoBot dump` -`@BotName ab` | Try to get 'abalabahaha'| `@NadekoBot ab` +### ClashOfClans +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` +`,startwar` `,sw` | Starts a war with a given number. | `,sw 15` +`,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]` +`,claimfinish1` `,cf1` | Finish your claim with 1 star if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `,cf1 1` or `,cf1 1 5` +`,claimfinish2` `,cf2` | Finish your claim with 2 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `,cf2 1` or `,cf2 1 5` +`,claimfinish` `,cf` | Finish your claim with 3 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. | `,cf 1` or `,cf 1 5` +`,endwar` `,ew` | Ends the war with a given index. | `,ew [war_number]` +`,unclaim` `,ucall` `,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]` -### Gambling -Command and aliases | Description | Usage -----------------|--------------|------- -`$draw` | Draws a card from the deck.If you supply number [x], she draws up to 5 cards from the deck. | `$draw [x]` -`$shuffle`, `$sh` | Reshuffles all cards back into the deck.|`$shuffle` -`$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3` -`$betflip`, `$bf` | Bet to guess will the result be heads or tails. Guessing award you double flowers you've bet. | `$bf 5 heads` or `$bf 3 t` -`$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. | `$rolluo` or `$rolluo 7` or `$rolluo 3d5` -`$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` -`$race` | Starts a new animal race. | `$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` -`$give` | Give someone a certain amount of NadekoFlowers|`$give 1 "@SomeGuy"` -`$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 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` -`$leaderboard`, `$lb` | Displays bot currency leaderboard | `$lb` +###### [Back to TOC](#table-of-contents) -### Games -Command and aliases | Description | Usage -----------------|--------------|------- -`>t` | Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. |`>t nohint` or `>t 5 nohint` -`>tl` | Shows a current trivia leaderboard. | `>tl` -`>tq` | Quits current trivia after current question. | `>tq` -`>typestart` | Starts a typing contest. | `>typestart` -`>typestop` | Stops a typing contest on the current channel. | `>typestop` -`>typeadd` | Adds a new article to the typing contest. Owner only. | `>typeadd wordswords` -`>poll` | Creates a poll, only person who has manage server permission can do it. **Needs Manage Server Permissions**| `>poll Question?;Answer1;Answ 2;A_3` -`>pollend` | Stops active poll on this server and prints the results in this channel. | `>pollend` -`>pick` | Picks a flower planted in this channel. | `>pick` -`>plant` | Spend a flower to plant it in this channel. (If bot is restarted or crashes, flower will be lost) | `>plant` -`>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` -`>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more` -`>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` -`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` +### CustomReactions +Command and aliases | Description | Usage +----------------|--------------|------- +`.addcustreact` `.acr` | Add a custom reaction with a trigger and a response. Running this command in server requires Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: | `.acr "hello" Hi there %user%` +`.listcustreact` `.lcr` | Lists global or server custom reactions (20 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. Specifying `all` argument instead of the number will DM you a text file with a list of all custom reactions. | `.lcr 1` or `.lcr all` +`.listcustreactg` `.lcrg` | Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. | `.lcrg 1` +`.showcustreact` `.scr` | Shows a custom reaction's response on a given ID. | `.scr 1` +`.delcustreact` `.dcr` | Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration priviledges and removes server custom reaction. | `.dcr 5` -### Music -Command and aliases | Description | Usage -----------------|--------------|------- -`!!next`, `!!n`, `!!skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!!n` -`!!stop`, `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s` -`!!destroy`, `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d` -`!!pause`, `!!p` | Pauses or Unpauses the song. | `!!p` -`!!queue`, `!!q`, `!!yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!!q Dream Of Venice` -`!!soundcloudqueue`, `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**. | `!!sq Dream Of Venice` -`!!listqueue`, `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2` -`!!nowplaying`, `!!np` | Shows the song currently playing. | `!!np` -`!!volume`, `!!vol` | Sets the music volume 0-100% | `!!vol 50` -`!!defvol`, `!!dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!!dv 80` -`!!mute`, `!!min` | Sets the music volume to 0% | `!!min` -`!!max` | Sets the music volume to 100%. | `!!max` -`!!half` | Sets the music volume to 50%. | `!!half` -`!!shuffle`, `!!sh` | Shuffles the current playlist. | `!!sh` -`!!playlist`, `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name` -`!!soundcloudpl`, `!!scpl` | Queue a soundcloud playlist using a link. | `!!scpl soundcloudseturl` -`!!localplaylst`, `!!lopl` | Queues all songs from a directory. **Bot Owner Only!** | `!!lopl C:/music/classical` -`!!radio`, `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `!!ra radio link here` -`!!local`, `!!lo` | Queues a local file by specifying a full path. **Bot Owner Only!** | `!!lo C:/music/mysong.mp3` -`!!move`, `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv` -`!!remove`, `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5` -`!!movesong`, `!!ms` | Moves a song from one position to another. | `!! ms 5>3` -`!!setmaxqueue`, `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq 50` or `!!smq` -`!!cleanup` | Cleans up hanging voice connections. **Bot Owner Only!** | `!!cleanup` -`!!reptcursong`, `!!rcs` | Toggles repeat of current song. | `!!rcs` -`!!rpeatplaylst`, `!!rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!!rpl` -`!!save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!!save classical1` -`!!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` -`!!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 30` -`!!getlink`, `!!gl` | Shows a link to the song in the queue by index, or the currently playing song by default. | `!!gl` -`!!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) | `!!ap` +###### [Back to TOC](#table-of-contents) -### Searches -Command and aliases | Description | Usage -----------------|--------------|------- -`~lolchamp` | Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. |`~lolchamp Riven` or `~lolchamp Annie sup` -`~lolban` | Shows top 6 banned champions ordered by ban rate. Ban these champions and you will be Plat 5 in no time. | `~lolban` -`~hitbox`, `~hb` | Notifies this channel when a certain user starts streaming. | `~hitbox SomeStreamer` -`~twitch`, `~tw` | Notifies this channel when a certain user starts streaming. | `~twitch SomeStreamer` -`~beam`, `~bm` | Notifies this channel when a certain user starts streaming. | `~beam SomeStreamer` -`~checkhitbox`, `~chhb` | Checks if a certain user is streaming on the hitbox platform. | `~chhb SomeStreamer` -`~checktwitch`, `~chtw` | Checks if a certain user is streaming on the twitch platform. | `~chtw SomeStreamer` -`~checkbeam`, `~chbm` | Checks if a certain user is streaming on the beam platform. | `~chbm SomeStreamer` -`~removestream`, `~rms` | Removes notifications of a certain streamer on this channel. | `~rms SomeGuy` -`~liststreams`, `~ls` | Lists all streams you are following on this server. | `~ls` -`~convert` | Convert quantities from>to. | `~convert m>km 1000` -`~convertlist` | List of the convertable dimensions and currencies. -`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke` -`~calculate`, `~calc` | Evaluate a mathematical expression. | `~calc 1+1` -`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko` -`~osu b` | Shows information about an osu beatmap. |`~osu b https://osu.ppy.sh/s/127712` -`~osu top5` | Displays a user's top 5 plays. |`~osu top5 Name` -`~pokemon`, `~poke` | Searches for a pokemon. | `~poke Sylveon` -`~pokemonability`, `~pokeab` | Searches for a pokemon ability. | `~pokeab "water gun"` -`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist` -`~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` -`~yt` | Searches youtubes and shows the first result | `~yt query` -`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. | `~aq aquarion evol` -`~imdb` | Queries imdb for movies or series, show first result. | `~imdb Batman vs Superman` -`~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. | `~mq Shingeki no kyojin` -`~randomcat`, `~meow` | Shows a random cat image. | `~meow` -`~randomdog`, `~woof` | Shows a random dog image. | `~woof` -`~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | `~i cute kitten` -`~ir` | Pulls a random image using a search parameter. | `~ir cute kitten` -`~lmgtfy` | Google something for an idiot. | `~lmgtfy query` -`~google`, `~g` | Get a google search link for some terms. | `~google query` -`~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera` -`~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple` -`~#` | Searches Tagdef.com for a hashtag. | `~# ff` -`~quote` | Shows a random quote. | `~quote` -`~catfact` | Shows a random catfact from | `~catfact` -`~yomama`, `~ym` | Shows a random joke from | `~ym` -`~randjoke`, `~rj` | Shows a random joke from | `~rj` -`~chucknorris`, `~cn` | Shows a random chucknorris joke from | `~cn` -`~magicitem`, `~mi` | Shows a random magicitem from | `~mi` -`~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. | `~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` -`~wiki` | Gives you back a wikipedia link | `~wiki query` -`~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` -`~videocall` | Creates a private 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 "@SomeGuy"` +### Gambling +Command and aliases | Description | Usage +----------------|--------------|------- +`$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. | `$raffle` or `$raffle RoleName` +`$cash` `$$$` | Check how much currency a person has. (Defaults to yourself) | `$$$` or `$$$ @SomeGuy` +`$give` | Give someone a certain amount of currency. | `$give 1 "@SomeGuy"` +`$award` | Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role. **Bot Owner only.** | `$award 100 @person` or `$award 5 Role Of Gamblers` +`$take` | Takes a certain amount of currency from someone. **Bot Owner only.** | `$take 1 "@someguy"` +`$betroll` `$br` | Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x3 and 100 x10. | `$br 5` +`$leaderboard` `$lb` | Displays bot currency leaderboard. | `$lb` +`$race` | Starts a new animal race. | `$race` +`$joinrace` `$jr` | Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win. | `$jr` or `$jr 5` +`$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 X normal dice (up to 30) unordered. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | `$rolluo` or `$rolluo 7` or `$rolluo 3d5` +`$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` +`$draw` | Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck. | `$draw` or `$draw 5` +`$shuffle` `$sh` | Reshuffles all cards back into the deck. | `$sh` +`$flip` | Flips coin(s) - heads or tails, and shows an image. | `$flip` or `$flip 3` +`$betflip` `$bf` | Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet. | `$bf 5 heads` or `$bf 3 t` -### NSFW -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` -`~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` -`~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` -`~cp` | We all know where this will lead you to. | `~cp` -`~boobs` | Real adult content. | `~boobs` -`~butts`, `~ass`, `~butt` | Real adult content. | `~butts` or `~ass` +###### [Back to TOC](#table-of-contents) -### ClashOfClans -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` -`,startwar`, `,sw` | Starts a war with a given number. | `,sw 15` -`,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]` -`,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]` -`,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 whose place to unclaim | `,uc [war_number] [optional_other_name]` -`,endwar`, `,ew` | Ends the war with a given index. | `,ew [war_number]` +### Games +Command and aliases | Description | Usage +----------------|--------------|------- +`>choose` | Chooses a thing from a list of things | `>choose Get up;Sleep;Sleep more` +`>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` +`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` +`>leet` | Converts a text to leetspeak with 6 (1-6) severity levels | `>leet 3 Hello` +`>poll` | Creates a poll which requires users to send the number of the voting option to the bot. **Requires ManageMessages server permission.** | `>poll Question?;Answer1;Answ 2;A_3` +`>publicpoll` `>ppoll` | Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. **Requires ManageMessages server permission.** | `>ppoll Question?;Answer1;Answ 2;A_3` +`>pollend` | Stops active poll on this server and prints the results in this channel. **Requires ManageMessages server permission.** | `>pollend` +`>cleverbot` | Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. **Requires ManageMessages server permission.** | `>cleverbot` +`>pick` | Picks the currency planted in this channel. | `>pick` +`>plant` | Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost) | `>plant` +`>gencurrency` `>gc` | Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%) **Requires ManageMessages server permission.** | `>gc` +`>typestart` | Starts a typing contest. | `>typestart` +`>typestop` | Stops a typing contest on the current channel. | `>typestop` +`>typeadd` | Adds a new article to the typing contest. **Bot Owner only.** | `>typeadd wordswords` +`>typelist` | Lists added typing articles with their IDs. 15 per page. | `>typelist` or `>typelist 3` +`>typedel` | Deletes a typing article given the ID. **Bot Owner only.** | `>typedel 3` +`>trivia` `>t` | Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. | `>t` or `>t 5 nohint` +`>tl` | Shows a current trivia leaderboard. | `>tl` +`>tq` | Quits current trivia after current question. | `>tq` -### Pokegame -Command and aliases | Description | Usage -----------------|--------------|------- -`>attack` | Attacks a target with the given move. Use `>movelist` to see a list of moves your type can use. | `>attack "vine whip" @someguy` -`>movelist`, `>ml` | Lists the moves you are able to use | `>ml` -`>heal` | Heals someone. Revives those who fainted. Costs a NadekoFlower | `>heal @someone` -`>type` | Get the poketype of the target. | `>type @someone` -`>settype` | Set your poketype. Costs a NadekoFlower. | `>settype fire` +###### [Back to TOC](#table-of-contents) -### Translator -Command and aliases | Description | Usage -----------------|--------------|------- -`~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` or `~translangs language` +### Help +Command and aliases | Description | Usage +----------------|--------------|------- +`-modules` `-mdls` | Lists all bot modules. | `-modules` +`-commands` `-cmds` | List all of the bot's commands from a certain module. You can either specify full, or only first few letters of the module name. | `-commands Administration` or `-cmds Admin` +`-help` `-h` | Either shows a help for a single command, or DMs you help link if no arguments are specified. | `-h !!q` or `-h` +`-hgit` | Generates the commandlist.md file. **Bot Owner only.** | `-hgit` +`-readme` `-guide` | Sends a readme and a guide links to the channel. | `-readme` or `-guide` +`-donate` | Instructions for helping the project financially. | `-donate` -### Customreactions -Command and aliases | Description | Usage -----------------|--------------|------- -`\o\` | Custom reaction. | `\o\` -`/o/` | Custom reaction. | `/o/` -`moveto` | Custom reaction. | `moveto` -`comeatmebro` | Custom reaction. | `comeatmebro` -`e` | Custom reaction. | `e` -`@BotName insult`, `<@!116275390695079945> insult` | Custom reaction. | `%mention% insult` -`@BotName praise`, `<@!116275390695079945> praise` | Custom reaction. | `%mention% praise` -`@BotName pat`, `<@!116275390695079945> pat` | Custom reaction. | `%mention% pat` -`@BotName cry`, `<@!116275390695079945> cry` | Custom reaction. | `%mention% cry` -`@BotName are you real?`, `<@!116275390695079945> are you real?` | Custom reaction. | `%mention% are you real?` -`@BotName are you there?`, `<@!116275390695079945> are you there?` | Custom reaction. | `%mention% are you there?` -`@BotName draw`, `<@!116275390695079945> draw` | Custom reaction. | `%mention% draw` -`@BotName bb`, `<@!116275390695079945> bb` | Custom reaction. | `%mention% bb` -`@BotName call`, `<@!116275390695079945> call` | Custom reaction. | `%mention% call` -`@BotName disguise`, `<@!116275390695079945> disguise` | Custom reaction. | `%mention% disguise` -`@BotName inv`, `<@!116275390695079945> inv` | Custom reaction. | `%mention% inv` -`@BotName threaten`, `<@!116275390695079945> threaten` | Custom reaction. | `%mention% threaten` -`@BotName archer`, `<@!116275390695079945> archer` | Custom reaction. | `%mention% archer` +###### [Back to TOC](#table-of-contents) -### Trello -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. **Bot Owner Only!**| `trello bind [board_id]` -`trello unbind` | Unbinds a bot from the channel and board. **Bot Owner Only!**| `trello unbind` -`trello lists`, `trello list` | Lists all lists, yo ;) **Bot Owner Only!**| `trello list` -`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. **Bot Owner Only!**| `trello cards index` +### Music +Command and aliases | Description | Usage +----------------|--------------|------- +`!!next` `!!n` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if !!rcs or !!rpl is enabled. | `!!n` or `!!n 5` +`!!stop` `!!s` | Stops the music and clears the playlist. Stays in the channel. | `!!s` +`!!destroy` `!!d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!!d` +`!!pause` `!!p` | Pauses or Unpauses the song. | `!!p` +`!!queue` `!!q` `!!yq` | Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. | `!!q Dream Of Venice` +`!!soundcloudqueue` `!!sq` | Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**. | `!!sq Dream Of Venice` +`!!listqueue` `!!lq` | Lists 15 currently queued songs per page. Default page is 1. | `!!lq` or `!!lq 2` +`!!nowplaying` `!!np` | Shows the song currently playing. | `!!np` +`!!volume` `!!vol` | Sets the music volume 0-100% | `!!vol 50` +`!!defvol` `!!dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `!!dv 80` +`!!shuffle` `!!sh` | Shuffles the current playlist. | `!!sh` +`!!playlist` `!!pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `!!pl playlist link or name` +`!!soundcloudpl` `!!scpl` | Queue a soundcloud playlist using a link. | `!!scpl soundcloudseturl` +`!!localplaylst` `!!lopl` | Queues all songs from a directory. **Bot Owner only.** | `!!lopl C:/music/classical` +`!!radio` `!!ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: ) | `!!ra radio link here` +`!!local` `!!lo` | Queues a local file by specifying a full path. **Bot Owner only.** | `!!lo C:/music/mysong.mp3` +`!!move` `!!mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!!mv` +`!!remove` `!!rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!!rm 5` +`!!movesong` `!!ms` | Moves a song from one position to another. | `!!ms 5>3` +`!!setmaxqueue` `!!smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `!!smq 50` or `!!smq` +`!!reptcursong` `!!rcs` | Toggles repeat of current song. | `!!rcs` +`!!rpeatplaylst` `!!rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!!rpl` +`!!save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!!save classical1` +`!!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` +`!!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 30` +`!!getlink` `!!gl` | Shows a link to the song in the queue by index, or the currently playing song by default. | `!!gl` +`!!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) | `!!ap` + +###### [Back to TOC](#table-of-contents) + +### NSFW +Command and aliases | Description | Usage +----------------|--------------|------- +`~hentai` | Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. | `~hentai yuri` +`~hentaibomb` | Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. | `~hentaibomb yuri` +`~yandere` | Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~yandere tag1+tag2` +`~danbooru` | Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~danbooru yuri+kissing` +`~konachan` | Shows a random hentai image from konachan with a given tag. Tag is optional but preferred. | `~konachan yuri` +`~gelbooru` | Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~gelbooru yuri+kissing` +`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (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 preferred. Use spaces for multiple tags. | `~e621 yuri kissing` +`~cp` | We all know where this will lead you to. | `~cp` +`~boobs` | Real adult content. | `~boobs` +`~butts` `~ass` `~butt` | Real adult content. | `~butts` or `~ass` + +###### [Back to TOC](#table-of-contents) + +### Permissions +Command and aliases | Description | Usage +----------------|--------------|------- +`;verbose` `;v` | Sets whether to show when a command/module is blocked. | `;verbose true` +`;permrole` `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr role` +`;listperms` `;lp` | Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions. | `;lp` or `;lp 3` +`;removeperm` `;rp` | Removes a permission from a given position in Permissions list. | `;rp 1` +`;moveperm` `;mp` | Moves permission from one position to another in Permissions list. | `;mp 2 4` +`;srvrcmd` `;sc` | Sets a command's permission at the server level. | `;sc "command name" disable` +`;srvrmdl` `;sm` | Sets a module's permission at the server level. | `;sm "module name" enable` +`;usrcmd` `;uc` | Sets a command's permission at the user level. | `;uc "command name" enable SomeUsername` +`;usrmdl` `;um` | Sets a module's permission at the user level. | `;um "module name" enable SomeUsername` +`;rolecmd` `;rc` | Sets a command's permission at the role level. | `;rc "command name" disable MyRole` +`;rolemdl` `;rm` | Sets a module's permission at the role level. | `;rm "module name" enable MyRole` +`;chnlcmd` `;cc` | Sets a command's permission at the channel level. | `;cc "command name" enable SomeChannel` +`;chnlmdl` `;cm` | Sets a module's permission at the channel level. | `;cm "module name" enable SomeChannel` +`;allchnlmdls` `;acm` | Enable or disable all modules in a specified channel. | `;acm enable #SomeChannel` +`;allrolemdls` `;arm` | Enable or disable all modules for a specific role. | `;arm [enable/disable] MyRole` +`;allusrmdls` `;aum` | Enable or disable all modules for a specific user. | `;aum enable @someone` +`;allsrvrmdls` `;asm` | Enable or disable all modules for your server. | `;asm [enable/disable]` +`;ubl` | Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. **Bot Owner only.** | `;ubl add @SomeUser` or `;ubl rem 12312312313` +`;cbl` | Either [add]s or [rem]oves a channel specified by an ID from a blacklist. **Bot Owner only.** | `;cbl rem 12312312312` +`;sbl` | Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist. **Bot Owner only.** | `;sbl add 12312321312` or `;sbl rem SomeTrashServer` +`;cmdcooldown` `;cmdcd` | Sets a cooldown per user for a command. Set to 0 to remove the cooldown. | `;cmdcd "some cmd" 5` +`;allcmdcooldowns` `;acmdcds` | Shows a list of all commands and their respective cooldowns. | `;acmdcds` +`;srvrfilterinv` `;sfi` | Toggles automatic deleting of invites posted in the server. Does not affect Bot Owner. | `;sfi` +`;chnlfilterinv` `;cfi` | Toggles automatic deleting of invites posted in the channel. Does not negate the ;srvrfilterinv enabled setting. Does not affect Bot Owner. | `;cfi` +`;srvrfilterwords` `;sfw` | Toggles automatic deleting of messages containing forbidden words on the server. Does not affect Bot Owner. | `;sfw` +`;chnlfilterwords` `;cfw` | Toggles automatic deleting of messages containing banned words on the channel. Does not negate the ;srvrfilterwords enabled setting. Does not affect bot owner. | `;cfw` +`;fw` | Adds or removes (if it exists) a word from the list of filtered words. Use`;sfw` or `;cfw` to toggle filtering. | `;fw poop` +`;lstfilterwords` `;lfw` | Shows a list of filtered words. | `;lfw` + +###### [Back to TOC](#table-of-contents) + +### Searches +Command and aliases | Description | Usage +----------------|--------------|------- +`~weather` `~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | `~we Moscow RF` +`~youtube` `~yt` | Searches youtubes and shows the first result | `~yt query` +`~imdb` `~omdb` | Queries omdb for movies or series, show first result. | `~imdb Batman vs Superman` +`~randomcat` `~meow` | Shows a random cat image. | `~meow` +`~randomdog` `~woof` | Shows a random dog image. | `~woof` +`~img` `~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | `~i cute kitten` +`~ir` | Pulls a random image using a search parameter. | `~ir cute kitten` +`~lmgtfy` | Google something for an idiot. | `~lmgtfy query` +`~shorten` | Attempts to shorten an URL, if it fails, returns the input URL. | `~shorten https://google.com` +`~google` `~g` | Get a google search link for some terms. | `~google query` +`~magicthegathering` `~mtg` | Searches for a Magic The Gathering card. | `~magicthegathering about face` or `~mtg about face` +`~hearthstone` `~hs` | Searches for a Hearthstone card and shows its image. Takes a while to complete. | `~hs Ysera` +`~urbandict` `~ud` | Searches Urban Dictionary for a word. | `~ud Pineapple` +`~#` | Searches Tagdef.com for a hashtag. | `~# ff` +`~catfact` | Shows a random catfact from | `~catfact` +`~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 Image link` +`~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) | `~safebooru yuri+kissing` +`~wikipedia` `~wiki` | Gives you back a wikipedia link | `~wiki query` +`~color` `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` +`~videocall` | Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `~videocall "@SomeGuy"` +`~avatar` `~av` | Shows a mentioned person's avatar. | `~av "@SomeGuy"` +`~bfonline` `~bfo` | Gives you online players for BF3 and BF4 | `~bfo bf3` or `~bfo bf4` +`~bfuser` `~bfu` | Gives you back a battlefield user's stats. | `~bfu platform game user` +`~wikia` | Gives you back a wikia link | `~wikia mtg Vigilance` or `~wikia mlp Dashy` +`~minecraftping` `~mcping` | Pings a minecraft server. | `~mcping 127.0.0.1:25565` +`~minecraftquery` `~mcq` | Finds information about a minecraft server. | `~mcq server:ip` +`~lolban` | Shows top banned champions ordered by ban rate. | `~lolban` +`~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist` +`~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"` +`~anime` `~ani` `~aq` | Queries anilist for an anime and shows the first result. | `~ani aquarion evol` +`~manga` `~mang` `~mq` | Queries anilist for a manga and shows the first result. | `~mq Shingeki no kyojin` +`~yomama` `~ym` | Shows a random joke from | `~ym` +`~randjoke` `~rj` | Shows a random joke from | `~rj` +`~chucknorris` `~cn` | Shows a random chucknorris joke from | `~cn` +`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke` +`~magicitem` `~mi` | Shows a random magicitem from | `~mi` +`~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko` +`~osub` | Shows information about an osu beatmap. | `~osub https://osu.ppy.sh/s/127712` +`~osu5` | Displays a user's top 5 plays. | `~osu5 Name` +`~placelist` | Shows the list of available tags for the `~place` command. | `~placelist` +`~place` | Shows a placeholder image of a given tag. Use `~placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments. | `~place Cage` or `~place steven 500 400` +`~pokemon` `~poke` | Searches for a pokemon. | `~poke Sylveon` +`~pokemonability` `~pokeab` | Searches for a pokemon ability. | `~pokeab overgrow` +`~hitbox` `~hb` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~hitbox SomeStreamer` +`~twitch` `~tw` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~twitch SomeStreamer` +`~beam` `~bm` | Notifies this channel when a certain user starts streaming. **Requires ManageMessages server permission.** | `~beam SomeStreamer` +`~liststreams` `~ls` | Lists all streams you are following on this server. | `~ls` +`~removestream` `~rms` | Removes notifications of a certain streamer on this channel. **Requires ManageMessages server permission.** | `~rms SomeGuy` +`~checkstream` `~cs` | Checks if a user is online on a certain streaming platform. | `~cs twitch MyFavStreamer` +`~translate` `~trans` | Translates from>to text. From the given language to the destination language. | `~trans en>fr Hello` +`~autotrans` `~at` | Starts automatic translation of all messages by users who set their `~atl` in this channel. You can set "del" argument to automatically delete all translated user messages. **Requires Administrator server permission.** **Bot Owner only.** | `~at` or `~at del` +`~autotranslang` `~atl` | `~atl en>fr` | Sets your source and target language to be used with `~at`. Specify no arguments to remove previously set value. +`~translangs` | Lists the valid languages for translation. | `~translangs` +`~xkcd` | Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and "latest" will get the latest one. | `~xkcd` or `~xkcd 1400` or `~xkcd latest` + +###### [Back to TOC](#table-of-contents) + +### Utility +Command and aliases | Description | Usage +----------------|--------------|------- +`.whosplaying` `.whpl` | Shows a list of users who are playing the specified game. | `.whpl Overwatch` +`.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 user-specific permissions on this channel. | `.checkmyperms` +`.userid` `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"` +`.channelid` `.cid` | Shows current channel ID. | `.cid` +`.serverid` `.sid` | Shows current server ID. | `.sid` +`.roles` | List roles on this server or a roles of a specific user if specified. Paginated. 20 roles per page. | `.roles 2` or `.roles @Someone` +`.channeltopic` `.ct` | Sends current channel's topic as a message. | `.ct` +`.stats` | Shows some basic stats for Nadeko. | `.stats` +`.showemojis` `.se` | Shows a name and a link to every SPECIAL emoji in the message. | `.se A message full of SPECIAL emojis` +`.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot Owner only.** | `.listservers 3` +`.calculate` `.calc` | Evaluate a mathematical expression. | `.calc 1+1` +`.calcops` | Shows all available operations in .calc command | `.calcops` +`.togethertube` `.totube` | Creates a new room on and shows the link in the chat. | `.totube` +`.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` +`.userinfo` `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser` +`.listquotes` `.liqu` | `.liqu` or `.liqu 3` | Lists all quotes on the server ordered alphabetically. 15 Per page. +`...` | Shows a random quote with a specified name. | `... abc` +`..` | Adds a new quote with the specified name and message. | `.. sayhi Hi` +`.deletequote` `.delq` | Deletes a random quote with the specified keyword. You have to either be server Administrator or the creator of the quote to delete it. | `.delq abc` +`.delallq` `.daq` | Deletes all quotes on a specified keyword. **Requires Administrator server permission.** | `.delallq kek` +`.remind` | Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. | `.remind me 1d5h Do something` or `.remind #general Start now!` +`.remindtemplate` | Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. **Bot Owner only.** | `.remindtemplate %user%, do %message%!` +`.convertlist` | List of the convertible dimensions and currencies. | `.convertlist` +`.convert` | Convert quantities. Use `.convertlist` to see supported dimensions and currencies. | `.convert m km 1000` diff --git a/docs/Contribution Guide.md b/docs/Contribution Guide.md index be1e29a3..2818756c 100644 --- a/docs/Contribution Guide.md +++ b/docs/Contribution Guide.md @@ -1,7 +1,7 @@ -### How to contribute - -1. Make Pull Requests to **DEV BRANCH** -2. Keep 1 Pull Request to a single feature -3. Explain what you did in the PR message - -Thanks for all the help ^_^ +### How to contribute + +1. Make Pull Requests to the **1.0 BRANCH** +2. Keep 1 Pull Request to a single feature +3. Explain what you did in the PR message + +Thanks for all the help ^_^ diff --git a/docs/Custom Reactions.md b/docs/Custom Reactions.md index 241a1bed..e5e9cc34 100644 --- a/docs/Custom Reactions.md +++ b/docs/Custom Reactions.md @@ -1,69 +1,40 @@ -##Custom Reactions -

Every time you add or remove a custom reaction you will need to restart your bot with either .restart if you are hosting or Nadeko is on VPS, or .die if Nadeko is on Droplet

-

Important

-
    -
  • .acr.dcr, and .ecr Require you to be Bot Owner
  • -
  • Adding multiple commands of the same name will make Nadeko randomly select one of the command's responses
  • -
-###Commands and Their Use - - - - - - - - - - - - - - - - - - - - - - - - -
Command NameDescriptionExample
.acrAdds a Custom Reaction.acr Hello Hi!
.dcrDeletes an entire Custom Reaction or a Specified Response Index Number.dcr "command name" or .dcr "command name" 3
.lcrLists a specified page of Custom Commands.lcr Number i.e .lcr 2
.scrShows all responses to a command. Index Number will be in brackets [ ].scr Hello
.ecrEdit a custom reaction, needs: reaction's name, index to change, and new multiword response..ecr Hello 3 What's up
-

Now that we know the commands let's take a look at the arguments and placeholders available for .acr,

-

.addcustreact (.acr) takes two arguments: -

-
● First, The name of the command; this directly follows the .acr
-
- If your command name is more than 1 word use quotation marks;
i.e .acr Nice weather sure is > .acr "Nice weather" sure is
-
- Now everytime someone says "Nice weather" the bot will respond with, "sure is"
-
● Next, The Response, this follows the name of the command.
-
- i.e .acr "Nice weather"sure is

-###And finally on to the Placeholders -

There are currently four placeholders, which we will be looking at, with many more to come in the future.

- - - - - - - - - - - - - - - - - - - - -
PlaceholderHow the placeholder worksExamples
%mention%The %mention% placeholder is triggered when you type @botname.acr "%mention% Hello" Hello! > User input: @botname Hello | Bot Replies: Hello!
%target%The %target% placeholder is used to make Nadeko Mention another person.acr "%mention% Hello" %target% Hi! > User inputs: "@botname Hello @somebody" - Bot replies: "Hi @somebody"
%user%The %user% placeholder mentions the person who said the command.acr "Who am i" You are %user%!
%rng%The %rng% generates a random number.acr Random %rng% -
- - Thanks to Nekai for being creative. <3 - +##Custom Reactions +###Important +* For modifying **global** custom reactions, the ones which will work across all the servers your bot is connected to, you **must** be a Bot Owner. +You must also use the commands for adding, deleting and listing these reactions in a direct message with the bot. +* For modifying **local** custom reactions, the ones which will only work on the server that they are added on, it is required to have the **Administrator** permission. +You must also use the commands for adding, deleting and listing these reactions in the server you want the custom reactions to work on. + +###Commands and Their Use +| Command Name | Description | Example | +|:------------:|-------------|---------| +|`.acr`|Add a custom reaction with a trigger and a response. Running this command in a server requries the Administrator permission. Running this command in DM is Bot Owner only, and adds a new global custom reaction. Guide [here](http://nadekobot.readthedocs.io/en/1.0/Custom%20Reactions/)|`.acr "hello" Hi there, %user%!`| +|`.lcr`|Lists a page of global or server custom reactions (15 reactions per page). Running this command in a DM will list the global custom reactions, while running it in a server will list that server's custom reactions.|`.lcr 1`| +|`.dcr`|Deletes a custom reaction based on the provided index. Running this command in a server requires the Administrator permission. Running this command in DM is Bot Owner only, and will delete a global custom reaction.|`.dcr 5`| + + +####Now that we know the commands let's take a look at an example of adding a command with `.acr`, +`.acr "Nice Weather" It sure is, %user%!` + +This command can be split into two different arguments: + +* The trigger, `"Nice Weather"` +* And the response, `It sure is, %user%!` + +An important thing to note about the triger is that, to be more than one word, we had to wrap it with quotation marks, `"Like this"` otherwise, only the first word would have been recognised as the trigger, and the second word would have been recognised as part of the response. + +There's no special requirement for the formatting of the response, so we could just write it in exactly the same way we want it to respond, albeit with a placeholder - which will be explained in this next section. + +Now, if that command was ran in a server, anyone on that server can make the bot mention them, saying `It sure is, @Username` anytime they say "Nice Weather". If the command is ran in a direct message with the bot, then the custom reaction can be used on every server the bot is connected to. + +###Placeholders! +There are currently three different placeholders which we will look at, with more placeholders potentially coming in the future. + +| Placeholder | Description | Example Usage | Usage | +|:-----------:|-------------|---------------|-------| +|`%mention`|The `%mention%` placeholder is triggered when you type `@BotName` - It's important to note that if you've given the bot a custom nickname, this trigger won't work!|```.acr "Hello %mention%" I, %mention%, also say hello!```|Input: "Hello @BotName" Output: "I, @BotName, also say hello!"| +|`%user%`|The `%user%` placeholder mentions the person who said the command|`.acr "Who am I?" You are %user%!`|Input: "Who am I?" Output: "You are @Username!"| +|`%rng%`|The `%rng%` placeholder generates a random number between 0 and 10|`.acr "Random number" %rng%`|Input: "Random number" Output: "2"| +|`%target%`|The `%target%` placeholder is used to make Nadeko Mention another person or phrase, it is only supported as part of the response|`.acr "Say this: " %target%`|Input: "Say this: I, @BotName, am a parrot!". Output: "I, @BotName, am a parrot!".| + + Thanks to Nekai for being creative. <3 diff --git a/docs/Frequently Asked Questions.md b/docs/Frequently Asked Questions.md index c472a08d..e3c30ba3 100644 --- a/docs/Frequently Asked Questions.md +++ b/docs/Frequently Asked Questions.md @@ -1,70 +1,77 @@ -#Frequently Asked Questions - - -###Question 1: How do I get Nadeko to join my server? ----- -**Answer:** Simply send Nadeko a Direct Message with -h and follow the link. **Only Server Owners can add the bot to the server** -###Question 2: I want to change permissions, but it isn't working! ----- -**Answer:** You must have the ;permsrole (by default this is the "Nadeko" role, for more details on permissions check [here](http://nadekobot.readthedocs.io/en/latest/Permissions%20System/ "Permissions")) - -**Please note:** *Only the Server Owner can change permissions without the "Nadeko" role*. -###Question 3: Music isn't working on Mac!! ----- -**Answer:** You will have to build `mono` from source. Simply follow the [mono-guide](http://www.mono-project.com/docs/compiling-mono/mac/ "Building mono"). -###Question 4: I want to disable NSFW on my server. ----- -**Answer:** To disable the NSFW Module for your server type, `;sm NSFW disable`. If this does not work refer to Question 2. -###Question 5: How do I get NadekoFlowers/Currency? ----- -**Answer:** You get NadekoFlowers by answering Trivia questions or picking them up after they have been generated with `>gc`, which you can then either plant (give away to a channel so that someone can pick it), gamble it with `$betflip`, `$betroll` and `$jr`, or spend on healing and setting your type in the Pokemon game. -###Question 6: I have an issue/bug/suggestion, where do I put it so it gets noticed? ------------ -**Answer:** First, check [issues](https://github.com/Kwoth/NadekoBot/issues "GitHub NadekoBot Issues"), then check the `#suggestions` in the Nadeko [help server](https://discord.gg/0ehQwTK2RBjAxzEY). - -If your problem or suggestion is not there, feel free to request it either in Issues or in `#suggestions`. -###Question 7: How do I use this command? --------- -**Answer:** You can see the description and usage of certain commands by using `-h command` **i.e** `-h ;sm`. - -The whole list of commands can be found [here](http://nadekobot.readthedocs.io/en/latest/Commands%20List/ "Command List") -###Question 8: Music isn't working? ----- -**Answer:** Music is disabled on public Nadeko, it will be re-enabled later in the future. - -**If you would like music you must host Nadeko yourself**. Be sure you have FFMPEG installed correctly, read the [guide](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/) for more info. -###Question 9: My music is still not working/very laggy? ----- -**Answer:** Try changing your discord [location][1], if this doesn't work be sure you have enabled the correct permissions for Nadeko. -[1]: https://support.discordapp.com/hc/en-us/articles/216661717-How-do-I-change-my-Voice-Server-Region- -###Question 10: I want to change data in the database (like NadekoFlowers or the pokemontypes of users, but how? ----- -**Answer:** Open data/nadekobot.sqlite using sqlitebrowser (or some alternative), Browse Data, select relevant table, change data, Write changes -###Question 11: The .greet and .bye commands doesn't work, but everything else is (From @Kong) ------ -**Answer:** Set a greeting message by using `.greetmsg YourMessageHere` and a bye-message by using `.byemsg YourMessageHere` -###Question 12: How do I import certs on linux? -------- -**Answer:** - -`certmgr -ssl https://discordapp.com` - -`certmgr -ssl https://gateway.discord.gg` -###Question 13: I want "BOT" tag with my bot a, is there a simple way? ----- -**Answer:** Yes, you can create an application using your account and use the APP BOT USER TOKEN from here: [DiscordApp][1] **NOTE: This will create a new bot account** -[1]:https://discordapp.com/developers/applications/me - -###Question 14: I made an application, but I can't add that new bot to my server, how do I invite it to my server? ----- -**Answer:** You need to use oauth link to add it to you server, just copy your CLIENT ID (that's in the same Developer page where you brought your token) and replace `12345678` in the link below: https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303 - -Follow this Detailed [Guide](http://discord.kongslien.net/guide.html) if you do not understand. -###Question 15:I'm building NadekoBot from source, but I get hundreds of (namespace) errors without changing anything!? ------ -**Answer:** Using Visual Studio, you can solve these errors by going to `Tools` -> `NuGet Package Manager` -> `Manage NuGet Packages for Solution`. Go to the Installed tab, select the Packages that were missing (usually `Newtonsoft.json` and `RestSharp`) and install them for all projects -###Question 16: My bot has all permissions but it's still saying, "Failed to add roles. Bot has insufficient permissions.". How do I fix this? ----------- -**Answer:** Discord has added a few new features and roles now follow hierarchy. This means you need to place your bot's role above every-other role your server has. - -**Please Note:** *The bot can only set/add all roles below its own highest role. It can not assign it's "highest role" to anyone else.* +#Frequently Asked Questions + + +###Question 1: How do I get Nadeko to join my server? +---- +**Answer:** Simply send Nadeko a Direct Message with -h and follow the link. **Only People with the Manage Server permission can add the bot to the server** + +###Question 2: I want to change permissions, but it isn't working! +---- +**Answer:** You must have the ;permrole (by default this is the "Nadeko" role, for more details on permissions check [here](http://nadekobot.readthedocs.io/en/1.0/Permissions%20System/ "Permissions"). If you have a role called `Nadeko` but can't assign it, just create a new Role and assign that instead.) + +###Question 3: I want to disable NSFW on my server. +---- +**Answer:** To disable the NSFW Module for your server type, `;sm NSFW disable`. If this does not work refer to Question 2. + +###Question 4: How do I get NadekoFlowers/Currency? +---- +**Answer:** You can get NadekoFlowers by picking them up after they have been generated with `>gc`, which you can then either plant (give away to a channel so that someone can pick it), or gamble with for potentinal profit with `$betflip`, `$betroll` and `$jr` + +###Question 5: I have an issue/bug/suggestion, where do I put it so it gets noticed? +----------- +**Answer:** First, check [issues](https://github.com/Kwoth/NadekoBot/issues "GitHub NadekoBot Issues"), then check the `#suggestions` channel in the Nadeko [help server](https://discord.gg/0ehQwTK2RBjAxzEY). + +If your problem or suggestion is not there, feel free to request/notify us about it either in the Issues section of GitHub for issues or in the `#suggestions` channel on the Nadeko help server for suggestions. + +###Question 6: How do I use this command? +-------- +**Answer:** You can see the description and usage of certain commands by using `-h command` **i.e** `-h ;sm`. + +The whole list of commands can be found [here](http://nadekobot.readthedocs.io/en/1.0/Commands%20List/ "Command List") + +###Question 7: Music isn't working? +---- +**Answer:** Music is disabled on public Nadeko due to large hosting costs, it will be re-enabled later in the future for donators. + +**If you would like music in the meantime, you must host Nadeko yourself**. Be sure you have FFMPEG installed correctly, and have followed the [guide](http://nadekobot.readthedocs.io/en/1.0/guides/Windows%20Guide/) carefully. + +###Question 8: My music is still not working/very laggy? +---- +**Answer:** Try changing your discord [location][1], if this doesn't work be sure you have enabled the correct permissions for Nadeko and rebooted since installing FFMPEG. +[1]: https://support.discordapp.com/hc/en-us/articles/216661717-How-do-I-change-my-Voice-Server-Region- + +###Question 9: I want to change data in the database (like NadekoFlowers or the pokemontypes of users, but how? +---- +**Answer:** Open `/data/NadekoBot.db` using sqlitebrowser (or some alternative), Browse Data, select relevant table, change data, Write changes + +###Question 10: The .greet and .bye commands doesn't work, but everything else is (From @Kong) +----- +**Answer:** Set a greeting message by using `.greetmsg YourMessageHere` and a bye-message by using `.byemsg YourMessageHere`. Don't forget that `.greet` and `.bye` only apply to users joining a server, not coming online/offline. + +###Question 11: How do I import certs on linux? +------- +**Answer:** + +`certmgr -ssl https://discordapp.com` + +`certmgr -ssl https://gateway.discord.gg` + +###Question 12: I made an application, but I can't add that new bot to my server, how do I invite it to my server? +---- +**Answer:** You need to use oauth link to add it to you server, just copy your CLIENT ID (that's in the same Developer page where you brought your token) and replace `12345678` in the link below: https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303 + +Follow this Detailed [Guide](http://discord.kongslien.net/guide.html) if you do not understand. + +###Question 13: I'm building NadekoBot from source, but I get hundreds of (namespace) errors without changing anything!? +----- +**Answer:** Using Visual Studio, you can solve these errors by going to `Tools` -> `NuGet Package Manager` -> `Manage NuGet Packages for Solution`. Go to the Installed tab, select the Packages that were missing (usually `Newtonsoft.json` and `RestSharp`) and install them for all projects + +###Question 14: My bot has all permissions but it's still saying, "Failed to add roles. Bot has insufficient permissions.". How do I fix this? +---------- +**Answer:** Discord has added a few new features and roles now follow hierarchy. This means you need to place your bot's role above every-other role your server has. [Here's](https://support.discordapp.com/hc/en-us/articles/214836687-Role-Management-101) a link to Discords role management 101. + +###Question 15: I've broken permissions and am stuck, can I reset permissions? +---------- +**Answer:** Yes, there is a way, in one easy command! Just run `.resetperms` + +**Please Note:** *The bot can only set/add all roles below its own highest role. It can not assign it's "highest role" to anyone else.* diff --git a/docs/JSON Explanations.md b/docs/JSON Explanations.md index 4b7316ce..8dbd9817 100644 --- a/docs/JSON Explanations.md +++ b/docs/JSON Explanations.md @@ -1,61 +1,73 @@ -###Setting up your Credentials -If you do not see `credentials.json` you will need to rename `credentials_example.json` to `credentials.json`. - -**This is how the unedited credentials look:** -```json -{ - "Token": "", - "ClientId": "116275390695079945", - "BotId": 1231231231231, - "OwnerIds": [ - 123123123123, - 5675675679845 - ], - "GoogleAPIKey": "", - "SoundCloudClientID": "", - "MashapeKey": "", - "LOLAPIKEY": "", - "TrelloAPPKey": "", - "OsuAPIKey": "", - "CarbonKey": "" -} -``` -####Required Parts -+ **Token** - Required to log in. Refer to this [guide](http://discord.kongslien.net/guide.html) -+ **OwnerIds** - Required for the **Owner-Only** commands. Seperate multiple Id's with a comma. -+ **BotId** - Required for custom reactions and conversation commands to work. - + **Important : Bot ID and Client ID are the same in newer bot accounts due to recent Discord API changes.** - -_BotId and the OwnerIds are **NOT** the names of the owner and the bot. If you do not know the id of your bot, keep the two random numbers in those fields and -run the bot then do `.uid @MyBotName` - this will give you your bot_id. -Do the same for yourself with `.uid @MyName` Put these numbers in their respective field of the credentials._ - -Setting up your API keys -==================== -####This part is completely optional, **However it is necessary for music to work properly** -+ **GoogleAPIKey** - Required for Youtube Song Search, Playlist queuing, and URL Shortener. `~i` and `~img`. - + You can get this api Key [here](https://console.developers.google.com/apis) -+ **SoundCloudClientID** - Required to queue soundloud songs from sc links. - + You will need to create a new app [here](http://soundcloud.com/you/apps). **Please note you must be logged into SoundCloud** - + Simply click Register a new application and enter a name. - + You will need to fill out an application form to request access to Soundcloud API. - + All requests for an API key must go through the review process, where applications will be reviewed on a case by case basis, in line with Soundcloud API Terms of Use. If your application is successful, you will receive an API key. - + Copy the Client ID and click "save app" then paste the Client Id it into your `credentials.json` -+ **MashapeKey** - Required for Urban Disctionary, Hashtag search, and Hearthstone cards. - + You need to create an account on their [api marketplace](https://market.mashape.com/), after that go to `market.mashape.com/YOURNAMEHERE/applications/default-application` and press **Get the keys** in the top right corner. - + Copy the key and paste it into `credentials.json` -+ **LOLAPIKey** - Required for all League of Legends commands. - + You can get this key [here](http://api.champion.gg/) -+ **TrelloAppKey** - Required for the trello commands. - + You can get this key [here](https://trello.com/app-key) **Be sure you are logged into Trello first** -+ **OsuAPIKey** - Required for Osu commands - + You can get this key [here](https://osu.ppy.sh/p/api) **You will need to log in and like the soundcloud it may take a few tries** -+ **CarbonKey** -This key is for Carobnitex.net stats. - + Most likely unnecessary **Needed only if your bot is listed on Carbonitex.net** - -Config.json -=========== -In the folder where `NadekoBot.exe` is located you should also see a `Data` folder. In this folder you will find `config.json` among other files. -`config.json` contains user specific commands, such as: if DM's sent to the bot are forwarded to you, Blacklisted Ids, Servers, and channels...etc. - -**If you do not see** `config.json` **you need to rename** `config_example.json` **to** `config.json` +###Setting up your Credentials +If you do not see `credentials.json` you will need to rename `credentials_example.json` to `credentials.json`. + +**This is how the unedited credentials look:** +```json +{ + "ClientId": 123123123, + "BotId": null, + "Token": "", + "OwnerIds": [ + 0 + ], + "LoLApiKey": "", + "GoogleApiKey": "", + "MashapeKey": "", + "OsuApiKey": "", + "SoundCloudClientId": "", + "CarbonKey": "", + "Db": null, + "TotalShards": 1 +} +``` +####Required Parts +- **Token** - Required to log in. Refer to this [guide](http://discord.kongslien.net/guide.html) +- **OwnerIds** - Required for the **Owner-Only** commands. Seperate multiple Id's with a comma. +- **BotId** - Required for custom reactions to work. + - **Important : Bot ID and Client ID will be the same in newer bot accounts due to recent changes by Discord.** + +_BotId and the OwnerIds are **NOT** the names of the owner and the bot. If you do not know the id of your bot, keep the two random numbers in those fields and +run the bot then do `.uid @MyBotName` - this will give you your bot_id. +Do the same for yourself with `.uid @MyName` Put these numbers in their respective field of the credentials._ + +Setting up your API keys +==================== +####This part is completely optional, **However it is necessary for music to work properly** +- **GoogleAPIKey** - Required for Youtube Song Search, Playlist queuing, and URL Shortener. `~i` and `~img`. + You can get this api Key [here](https://console.developers.google.com/apis) +- **SoundCloudClientID** - Required to queue soundloud songs from sc links. + You will need to create a new app [here](http://soundcloud.com/you/apps). **Please note you must be logged into SoundCloud** + - Simply click Register a new application and enter a name. + - Copy the Client ID and click "save app" then paste the Client Id it into your `credentials.json` +- **MashapeKey** - Required for Urban Disctionary, Hashtag search, and Hearthstone cards. + You need to create an account on their [api marketplace](https://market.mashape.com/), after that go to `market.mashape.com/YOURNAMEHERE/applications/default-application` and press **Get the keys** in the top right corner. + - Copy the key and paste it into `credentials.json` +- **LOLAPIKey** - Required for all League of Legends commands. + You can get this key [here](http://api.champion.gg/) +- **OsuAPIKey** - Required for Osu commands + You can get this key [here](https://osu.ppy.sh/p/api) **You will need to log in and like the soundcloud it may take a few tries** +- **CarbonKey** -This key is for Carobnitex.net stats. + Most likely unnecessary **Needed only if your bot is listed on Carbonitex.net** + +Additional options +==================== +- **TotalShards** - Required if the bot will be connected to more than 2500 servers + Most likely unnecessary to change until your bot is added to more than 2000 servers +[//]: # (- **Db** - Allows for advanced database configuration ) +[//]: # ( - Leave this with the `null` value for standard operation - change this to `examples` to [This is only a comment so doesn't need proper detail]) + + +Config.json +=========== +`config.json` is now removed with the addition of `NadekoBot.db` so if you have Nadeko 0.9x follow the [upgrading guide](http://nadekobot.readthedocs.io/en/latest/guides/Upgrading%20Guide/) + +DB files +======== +Nadeko uses few db files in order to open the database +- `NadekoBot\src\NadekoBot\bin\Release\netcoreapp1.0\data\NadekoBot.db` (1.0) +- `data/NadekoBot.sqlite` (0.9x) +you will need [DB Browser for SQLite](http://sqlitebrowser.org/) to make changes to any settings you want and click on "write changes" at the top. + +![nadekodb](https://cdn.discordapp.com/attachments/251504306010849280/254067055240806400/nadekodb.gif) + +[CleverBot APIs]: https://cleverbot.io/keys diff --git a/docs/Permissions System.md b/docs/Permissions System.md index 321c5c4f..11c32daf 100644 --- a/docs/Permissions System.md +++ b/docs/Permissions System.md @@ -1,101 +1,84 @@ -Permissions Overview -=================== -Have you ever felt confused or even overwhelmed when trying to set Nadeko's permissions? In this guide we will be explaining how to use the -permission commands correctly and even cover a few common questions! Every command we discuss here can be found in the [Commands List](http://nadekobot.readthedocs.io/en/latest/Commands%20List/#permissions). - -Why do we use the Permissions Commands? ------------------------------- -Permissions are very handy for setting who can use what commands in your server. By default, every command is enabled for everybody, however a few exclusions are the Administration Commands like, `.kick` and `.prune` and Bot Owner-Only commands as these require your id to be in [`credentials.json`](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/ "Setting up your credentials"). -With the Permission Commands it is possible to restrict who can skip the current song, pick NadekoFlowers, or even use the NSFW module. - -First Time Setup ------------------- -To change permissions you must meet one of two requirements, either: -+ **Be the Server Owner** -+ Have the role specified by `;permrole` (is Nadeko by default). - -If you meet neither of these requirements, you ***can not*** edit permissions. - -If you would like to allow Admins to edit permissions you may want to rename the `;permrole` to `;permrole Admins` and give each admin that role. - -Basics & Heirachy ------ -Most of the commands found in the list describe what they do, but we will cover a few here. - -If you would like to disable the NSFW module for a certain role you would use `;rolemdl NSFW disable SomeRole`. - -Similarly you can view which Modules and Commands are banned on a _specific_ channel with `;chnlperms SomeChannel`. - -The heirachy of the Permissions are simple. If you disable a Module/command with a command that affects the server, `;sm NSFW disable` you can not then enable it another way such as, `;cm NSFW enable SomeChannel`. Roles are an exemption to this, i.e if all roles except for the DJ Role have music disabled, you can still use music commands if you have the DJ Role. - -The bot, by default will notify you when a command can't be used. To disable this notification simply use `;verbose false`. - - -Commonly Asked Questions ---------------- -###How do I create a music DJ? -To allow users to only see the current song and have a DJ role for queuing follow these five steps: - - 1. `;arc music disable all` -1. Disables all music commands for everyone. - 2. `;arc music enable DJ` -1. Gives all music commands to DJ role. - 3. `;rc !!nowplaying enable all` -1. Enables the "nowplaying" command for eveone. - 4. `;rc !!getlink enable all` -1. Enables the "getlink" command for everyone. - 5. `;rc !!listqueue enable all` -1. Enables the "listqueue" command for everyone. - -###How do I create an NSFW channel? -You want to only allow NSFW commands in the #nsfw channel. - `;cm nsfw disable all` disable the nsfw module in every channel. - `;cm nsfw enable #nsfw` re-enable the nsfw module in the #nsfw channel. - -_-- Thanks to @applemac for writing this guide_ - -Old Guide ---------- -**NadekoBot's permissions can be set up to be very specific through commands in the Permissions module.** - -Each command or module can be turned on or off at: -- The user level (so specific users can or cannot use a command/module) -- The role level (so only certain roles have access to certain commands/module) -- The channel level (so certain commands can be limited to certain channels, which can prevent music / trivia / NSFW spam in serious channels) -- The server level. - -Use `.modules` to see a list of modules (sets of commands). Use `.commands [module_name]` to see a list of commands in a certain module. - -Permissions use a semicolon as the prefix, so always start the command with a `;`. - -Follow the semicolon with the letter of the level which you want to edit: -- "u" for Users. -- "r" for Roles. -- "c" for Channels. -- "s" for Servers. - -Follow the level with whether you want to edit the permissions of a command or a module. -- "c" for Command. -- "m" for Module. - -Follow with a space and then the command or module name (surround the command with quotation marks if there is a space within the command, for example "!!q" or "!!n"). - -Follow that with another space and, to enable it, type one of the following: [1, true, t, enable], or to disable it, one of the following: [0, false, f, disable]. - -Follow that with another space and the name of the user, role, channel. (depending on the first letter you picked) - -####Examples #1 - -- `;rm NSFW 0 [Role_Name]` Disables the NSFW module for the role, -- `;cc "!!n" 0 [Channel_Name]` Disables skipping to the next song in the channel, -- `;uc "!!q" 1 [User_Name]` Enables queuing of songs for the user, -- `;sm Gambling 0 Disables` gambling in the server. - - Check permissions by using the letter of the level you want to check followed by a p, and then the name of the level in which you want to check. If there is no name, it will default to yourself for users, the @everyone role for roles, and the channel in which the command is sent for channels. - -####Examples #2 - -- `;cp [Channel_Name]` -- `;rp [Role_Name]` - -Insert an "a" before the level to edit the permission for all commands / modules for all users / roles / channels / server. - -Reference the Help command (-h) for more Permissions related commands. +Permissions Overview +=================== +Have you ever felt confused or even overwhelmed when trying to set Nadeko's permissions? In this guide we will be explaining how to use the +permission commands correctly and even cover a few common questions! Every command we discuss here can be found in the [Commands List](http://nadekobot.readthedocs.io/en/1.0/Commands%20List/#permissions). + +**To see the old guide for versions 0.9 and below, see [here](http://nadekobot.readthedocs.io/en/latest/Permissions%20System/)** + +Why do we use the Permissions Commands? +------------------------------ +Permissions are very handy at setting who can use what commands in a server. By default, the NSFW module is blocked, but nothing else is. If something is a bot owner only command, it can only be ran by the bot owner, the person who is running the bot, or has their ID in [`credentials.json`](http://nadekobot.readthedocs.io/en/1.0/JSON%20Explanations/ "Setting up your credentials"). + +The Administration module still requires that you have the correct permissions on Discord to be able to use these commands, so for users to be able to use commands like `.kick` and `.prune`, they need kick and mange messages permissions respectively. + +With the permissions system it possible to restrict who can skip the current song, pick NadekoFlowers or use the NSFW module. + +First Time Setup +------------------ +To change permissions you **must** meet the following requirement: + +**Have the role specified by `;permrole` (By default, this is Nadeko)** + +If you have an existing role called `Nadeko` but can't assign it to yourself, create a new role called `Nadeko` and assign that to yourself. + +If you would like to set a different role, such as `Admins`, to be the role required to edit permissions, do `;permrole Admins` (you must have the current permission role to be able to do this). + +Basics & Hierarchy +----- +The [Commands List](http://nadekobot.readthedocs.io/en/1.0/Commands%20List/#permissions) is a great resource which lists **all** the available commands, however we'll go over a few commands here. + +Firstly, let's explain how the permissions system works - It's simple once you figure out how each command works! +The permissions system works as a chain, everytime a command is used, the permissions chain is checked. Starting from the top of the chain, the command is compared to a rule, if it isn't either allowed or disallowed by that rule it proceeds to check the next rule all the way till it reaches the bottom rule, which allows all commands. + +To view this permissions chain, do `;listperms`, with the top of the chain being rule number 1, shown at the top of the message. + +If you want to remove a permission from the chain of permissions, do `;removeperm X` to remove rule number X and similarly, do `;moveperm X Y` to move rule number X to number Y (moving, not swapping!). + +As an example, if you wanted to enable NSFW for a certain role, say "Lewd", you could do `;rolemdl NSFW enable Lewd`. +This adds the rule to the top of the permissions chain so even if the default `;sm NSFW disabled` rule exists, the "Lewd" role will be able to use the NSFW module. + +If you want the bot to notify users why they can't use a command or module, use `;verbose true` and Nadeko will tell you what rule is preventing the command. + +Commonly Asked Questions +--------------- + +###How do I create a music DJ? +To allow users to only see the current song and have a DJ role for queuing follow these five steps: + +1. `;sm Music disable` + + * Disables music commands for everybody + +2. `;sc !!nowplaying enable` + + * Enables the "nowplaying" command for everyone + +3. `;sc !!getlink enable` + + * Enables the "getlink" command for everyone + +4. `;sc !!listqueue enable` + + * Enables the "listqueue" command for everyone + +5. `;rm Music enable DJ` + + * Enables all the music commands only for the DJ role + + +###How do I create a NSFW channel? +Say you want to only enable NSFW commands in the #NSFW channel, just do the following two steps. + +1. `;sm NSFW disable` + * Disables the NSFW module from being used + +2. `;cm NSFW enable #NSFW` + * Enables the NSFW module for use in the #NSFW channel + +###I've broken permissions and am stuck, can I reset permissions? +Yes, there is a way, in one easy command! + +1. `.resetperms` + * This resets the permission chain back to default, with only NSFW disabled + +_-- Thanks to @applemac for providing the template for this guide_ diff --git a/docs/Readme.md b/docs/Readme.md index a622fa60..4268c8c3 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -1,29 +1,29 @@ -##Readme for Commands List - -###Bot Owner Only - -- *Bot Owner Only* commands refer to the commands only the **owner** of the bot can use. -- *Bot Owner Only* commands do **not** refer to the owner of the **server**, just the owner of the **bot**. -- *Owner of the bot* is a person who is **hosting** his own bot, and his **ID** is inside of **credentials.json** file. -- You are **not** the bot **owner** if you invited the bot using **Carbonitex** or other invitation links. - -###Music on the public Nadeko - -- In case you got Nadeko in your server by the invitation from **Carbonitex**, our **GitHub** invite or **help (-h)**, music is disabled. -- Music is **disabled** due to large maintenance expenses, unless Kwoth is **testing** music module. -- If you want to have music module on your server, you will have to **host** the bot on your PC, or any of the external servers. -- How to **host** the bot, check the **guides** on the left side. - -###NadekoFlowers - -- NadekoFlowers is the **currency** of the public Nadeko. -- NadekoFlowers can be earned by playing **trivia**, or other games, like betting games. -- You can give NadekoFlowers to other users, using the command `$give X @person`. -- You can only give flowers you **own**. -- If you want to have **unlimited** number of flowers, you will have to **host** the bot. -- Command `$award X @person` can only be used by the *bot owner*. -- If you `>plant` the flower, flower will be avaliable for everyone to `>pick` it. In that case you will **lose** the flower. - -###Manage Permissions - -**These permissions refer to the permissions you can set in Discord settings for individual users or roles.** +##Readme for Commands List + +###Bot Owner Only + +- *Bot Owner Only* commands refer to the commands only the **owner** of the bot can use. +- *Bot Owner Only* commands do **not** refer to the owner of the **server**, just the owner of the **bot**. +- *Owner of the bot* is a person who is **hosting** their own bot, and their **ID** is inside of **credentials.json** file. +- You are **not** the bot **owner** if you invited the bot using **Carbonitex** or other invitation links. + +###Music on the public Nadeko + +- In case you got Nadeko in your server by the invitation from **Carbonitex**, our **GitHub** invite or **help (-h)**, music is disabled. +- Music is **disabled** due to large maintenance expenses, unless Kwoth is **testing** music module. +- If you want to have music module on your server, you will have to **host** the bot on your PC, or any of the external servers. +- How to **host** the bot, check the **guides** on the left side. + +###NadekoFlowers + +- NadekoFlowers is the **currency** of the public Nadeko. +- NadekoFlowers can be `>pick`ed after Nadeko plants a flower randomly after `>gc` has been used on a channel +- You can give NadekoFlowers to other users, using the command `$give X @person`. +- You can only give flowers you **own**. +- If you want to have **unlimited** number of flowers, you will have to **host** the bot. +- Commands `$award X @person` and `$take X @person` can only be used by the *bot owner*. +- If you `>plant` the flower, flower will be avaliable for everyone to `>pick` it. In that case you will **lose** the flower. + +###Manage Permissions + +**These permissions refer to the permissions you can set in Discord settings for individual users or roles.** diff --git a/docs/about.md b/docs/about.md index 21f82c06..8b784e4c 100644 --- a/docs/about.md +++ b/docs/about.md @@ -1,13 +1,28 @@ -## Terms of Use - -The MIT License (MIT) - -Copyright (c) 2016 NadekoBot Team - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - +## Terms of Use + +**The Unlicense License** + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/docs/guides/Building from Source.md b/docs/guides/Building from Source.md deleted file mode 100644 index 16e4eb3a..00000000 --- a/docs/guides/Building from Source.md +++ /dev/null @@ -1,8 +0,0 @@ -###Building from Source -For easy setup and no programming knowledge, you can use [Updater](https://github.com/Kwoth/NadekoUpdater/releases/latest) or download release from [Releases](https://github.com/Kwoth/NadekoBot/releases) and follow the [Windows Guide](Windows Guide.md) - -In your bin/debug folder (or next to your exe if you are using release version), you must have a file called 'credentials.json' in which you will store all the necessary data to make the bot know who the owner is, and your api keys. - -When you clone the project, make sure to run `git submodule init` and `git submodule update` to get the correct discord.net version - -Make sure you've read ComprehensiveGuide to get a grasp of basic config/credentials setup and then look at "Credentials and config" chapter. diff --git a/docs/guides/Docker Guide.md b/docs/guides/Docker Guide.md index 74dab8f3..00a13a99 100644 --- a/docs/guides/Docker Guide.md +++ b/docs/guides/Docker Guide.md @@ -1,54 +1,3 @@ -# Docker Guide with DigitalOcean - -#####Prerequisites -- Digital ocean account (you can use my [reflink][reflink] to support the project and get 10$ after you register) -- [PuTTY][PuTTY] -- A bot account - follow this [guide][guide] -- $5 -- Common sense - -#####Guide -- Click on the create droplet button -![img](http://i.imgur.com/g2ayOcC.png) - -- Pick one click apps and select docker on 14.04 - -![img](http://imgur.com/065Xkme.png) - -- Pick any droplet size you want (5$ will work ok-ish on a few servers) -- Pick location closest to your discord server's location -- Pick a hostname -![img](http://imgur.com/ifPKB6p.png) - -- Click create - -You will get an email from DigitalOcean with your credentials now. - -Open putty and type ip adress **you got in your email** with port 22 - -![img](http://imgur.com/Mh5ehsh.png) - -- Console will open and you will be prompted for a username, type `root`. -- Type in the password you got in the email. -- Confirm the password you just typed in. -- Type in the new password. -- Confirm new password. - -- When you are successfully logged in, type -`docker run --name nadeko -v /nadeko:/config uirel/nadeko` - -- Wait for it to download and at one point it is going to start throwing errors due to `credentials.json` being empty -- CTRL+C to exit that -- Type `docker stop nadeko` -- Type `nano /nadeko/credentials.json` and type in your `credentials` -- CTRL+X then CTRL+Y to save -- Type `docker start nadeko` -- Type `docker logs -f nadeko` to see the console output - -**Your bot is running, enjoy! o/** - -*When you want to update the bot, just type `docker restart nadeko` as it always downloads latest prerelease* - -[reflink]: http://m.do.co/c/46b4d3d44795/ -[PuTTY]: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html -[guide]: http://discord.kongslien.net/guide.html +# Docker Guide with DigitalOcean + +## There is no docker image for 1.0 nadeko right now. Soon. \ No newline at end of file diff --git a/docs/guides/Linux Guide.md b/docs/guides/Linux Guide.md index d1d5e24c..b917c9ca 100644 Binary files a/docs/guides/Linux Guide.md and b/docs/guides/Linux Guide.md differ diff --git a/docs/guides/OSX Guide.md b/docs/guides/OSX Guide.md index 4fed4867..00042cfd 100644 --- a/docs/guides/OSX Guide.md +++ b/docs/guides/OSX Guide.md @@ -1,21 +1,23 @@ -### Setting Up NadekoBot on OSX +## Setting Up NadekoBot on OSX + #### Prerequisites -- 1) [Homebrew][Homebrew] -- 2) Mono -- 3) Google Account -- 4) Soundcloud Account (if you want soundcloud support) -- 5) Text Editor (TextWrangler, or equivalent) or outside editor such as [Atom][Atom] +- [Homebrew][Homebrew] +- Google Account +- Soundcloud Account (if you want soundcloud support) +- Text Editor (TextWrangler, or equivalent) or outside editor such as [Atom][Atom] ####Installing Homebrew -`/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"` +```/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"``` + +Run `brew update` to fetch the latest package data. -Run `brew update` to fetch the latest package data. ####Installing dependencies ``` brew install git brew install ffmpeg brew update && brew upgrade ffmpeg +brew install openssl brew install opus brew install opus-tools brew install opusfile @@ -24,9 +26,17 @@ brew install libsodium brew install tmux ``` +####Installing .NET Core SDK + +- `ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/` +- `ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/` +- Download the [.NET Core SDK](https://www.microsoft.com/net/core#macos), found [here.](https://go.microsoft.com/fwlink/?LinkID=835011) +- Open the `.pkg` file you downloaded and install it. +- `ln -s /usr/local/share/dotnet/dotnet /usr/local/bin` + ####Check your `FFMPEG` -**In case your `FFMPEG` wasnt installed properly** +**In case your `FFMPEG` wasnt installed properly (Optional)** - `brew options ffmpeg` - `brew install ffmpeg --with-x --with-y --with-z` etc. @@ -36,130 +46,126 @@ brew install tmux - Then try `brew install ffmpeg` again. ####Installing xcode-select + Xcode command line tools. You will do this in Terminal.app by running the following command line: `xcode-select --install` A dialog box will open asking if you want to install `xcode-select`. Select install and finish the installation. -####Installing Mono -- Building Mono dependencies: +####Downloading and building Nadeko -`brew install autoconf automake libtool pkg-config` +Use the following command to get and run `linuxAIO.sh`: +(Remember **DO NOT** rename the file `linuxAIO.sh`) -- Building Mono from Source: - -To build Mono from a Git Source Code checkout, you will want to have the official Mono installed on the system, as the build requires a working C# compiler to run. Once you do this, run the following commands, remember to replace PREFIX with your installation prefix that you selected: +`cd ~ && wget https://github.com/Kwoth/NadekoBot-BashScript/raw/master/linuxAIO.sh && bash linuxAIO.sh` -``` -PATH=$PREFIX/bin:$PATH -git clone https://github.com/mono/mono.git -cd mono -CC='cc -m32' ./autogen.sh --prefix=$PREFIX --disable-nls --build=i386-apple-darwin11.2.0 -make -make install -``` -To build Mono in 64 bit mode instead use this to configure the build: +Follow the on screen instructions: -`./autogen.sh --prefix=$PREFIX --disable-nls` +1. To Get the latest build. (most recent updates) +2. To Get the stable build. -####Nadeko Setup -- Create a new folder and name it `Nadeko`. -- Move to our `Nadeko` folder - -`cd Nadeko` -- Go to [Releases][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` -- Get the correct link, type `curl -O` and past the link, then hit `Enter` -- It should be something like this: - -`curl -O https://github.com/Kwoth/NadekoBot/releases/download/vx.xx/NadekoBot.vx.x.zip` +Choose either `1` or `2` then press `enter` key. +Once Installation is completed you should see the options again. +Next, choose `5` to exit. -^ do not copy-paste it +####Creating and Inviting bot -- Unzip the downloaded file in our `Nadeko` folder - -####Creating DiscordBot application -- Go to [DiscordApp][DiscordApp]. -- Log in with your Discord account. -- On the left side, press `New Application`. -- Fill out the `App Name` (your bot's name, in this case), put the image you want, and add an app description(optional). -- Create the application. -- Once the application is created, click on `Create a Bot User` and confirm it. -- Keep this window open for now. +- Read here how to [create a DiscordBot application](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#creating-discordbot-application) +- [Visual Invite Guide](http://discord.kongslien.net/guide.html) *NOTE: Client ID is your Bot ID* +- Copy your `Client ID` from your [applications page](https://discordapp.com/developers/applications/me). +- Replace the `12345678` in this link `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with your `Client ID`. +- The link should now look like this: `https://discordapp.com/oauth2/authorize?client_id=**YOUR_CLENT_ID_HERE**&scope=bot&permissions=66186303`. +- Go to the newly created link and pick the server we created, and click `Authorize`. +- The bot should have been added to your server. ####Setting up Credentials.json file -- In our `NadekoBot` folder you should have `.json` file named `credentials_example.json`. (Note: If you do not see a **.json** after `credentials_example.json `, do not add the `**.json**`. You most likely have `"Hide file extensions"` enabled.) -- Rename `credentials_example.json` to `credentials.json`. -- Open the file with your Text editor. -- In there you will see fields like `Token`, `ClientId`, `BotId`, `OwnerIDs`. -- In your [DiscordApp][DiscordApp], under `Bot User` part, you will see the `Token:click to reveal` part, click to reveal it. -- Copy your bot's token, and put it between `" "` in your `credentials.json` file. -- Copy `Client ID` and replace it with the example one in your `credentials.json` in `Client ID` **and** `BotID` field. -- Save your `credentials.json` but keep it open. We need to put your `User ID` and owner. - -####Running NadekoBot -- Copy/past and hit `Enter` - -`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)`. - -or if you want to use Screen, run: - -`screen -S nadeko` - -^this will create a new screen named “nadeko” `(you can replace “nadeko” with anything you prefer and remember its your -screen name)`. - -`cd nadeko` - -- Start NadekoBot.exe using Mono: - -`mono NadekoBot.exe` - -CHECK THE BOT IN DISCORD, IF EVERYTHING IS WORKING - -Now time to move bot to background and to do that, press CTRL+B+D (this will ditach the nadeko session using TMUX) - -*if you used Screen press CTRL+A+D (this will detach the nadeko screen)* - -####Inviting your bot to your server - [Invite Guide][Invite Guide] -- Create a new server in Discord. -- Copy your `Client ID` from your [DiscordApp][DiscordApp]. -- Replace `12345678` in this link `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with your `Client ID`. -- Link should look like this: `https://discordapp.com/oauth2/authorize?client_id=**YOUR_CLENT_ID**&scope=bot&permissions=66186303`. -- Go to newly created link and pick the server we created, and click `Authorize`. -- Bot should be added to your server. - -####Setting up OwnerIds -- In the server where your bot is, in a text channel, type `.uid` -- Your `User ID` should show, copy it. -- Close `NadekoBot.exe` -- Replace your `User ID` in the `credentials.json` between `[ ]` and save the changes. -- Run `NadekoBot.exe` again. -- Now you are the bot owner. -- You can add `User IDs` from the other users by separating IDs with a comma if you want to have more owners. +- Open up the `NadekoBot` folder, which should be in your home directory, then `NadekoBot` folder then `src` folder and then the additonal `NadekoBot` folder. +- EDIT it as it is guided here: [Setting up credentials.json](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-credentialsjson-file) +- **If** you already have Nadeko 1.0 setup and have `credentials.json` and `NadekoBot.db`, you can just copy and paste the `credentials.json` to `NadekoBot/src/NadekoBot` and `NadekoBot.db` to `NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.0/data`. +- **If** you have Nadeko 0.9x follow the [Upgrading Guide](http://nadekobot.readthedocs.io/en/latest/guides/Upgrading%20Guide/) ####Setting NadekoBot Music -For Music Setup and API keys check [Setting up NadekoBot for Music](Windows Guide.md#setting-up-nadekobot-for-music) and [JSON Explanations](JSON Explanations.md). +For Music Setup and API keys check [Setting up NadekoBot for Music](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#setting-up-nadekobot-for-music) and [JSON Explanations](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/). -####Some more Info - TMUX +####Running NadekoBot --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 kill NadekoBot session, type `tmux kill-session -t nadeko` +- Using tmux -####Some more Info - Screen +`tmux new -s nadeko` --If you want to see the sessions after logging back again, type `screen -ls`, and that will give you the list of screens. --If you want to switch to/ see that screen, type `screen -r nadeko` (nadeko is the name of the screen we created before so, replace `“nadeko”` with the screen name you created.) --If you want to kill the NadekoBot screen, type `screen -X -S nadeko quit` +^this will create a new session named “nadeko” +`(you can replace “nadeko” with anything you prefer and remember its your session name)`. + +- Using Screen + +`screen -S nadeko` + +^this will create a new screen named “nadeko” +`(you can replace “nadeko” with anything you prefer and remember its your screen name)`. + +- Start Nadeko using .NET Core: + +`cd ~ && bash linuxAIO.sh` + +From the options, + +Choose `3` To Run the bot normally. +**NOTE:** With option `3` (Running Normally), if you use `.die` [command](http://nadekobot.readthedocs.io/en/latest/Commands%20List/#administration) in discord. The bot will shut down and will stay offline untill you manually run it again. (best if you want to check the bot.) + +Choose `4` To Run the bot with Auto Restart. +**NOTE:** With option `4` (Running with Auto Restart), bot will auto run if you use `.die` [command](http://nadekobot.readthedocs.io/en/latest/Commands%20List/#administration) making the command `.die` to be used as restart. +**NOTE:** [To stop the bot you will have to kill the session.](http://nadekobot.readthedocs.io/en/latest/guides/OSX%20Guide/#some-more-info) + +**Now check your Discord, the bot should be online** + +Now time to move bot to background and to do that, press CTRL+B+D (this will detach the nadeko session using TMUX) +If you used Screen press CTRL+A+D (this will detach the nadeko screen) + +####Updating Nadeko + +- Connect to the terminal. +- `tmux kill-session -t nadeko` [(don't forget to replace **nadeko** in the command to what ever you named your bot's session)](http://nadekobot.readthedocs.io/en/latest/guides/OSX%20Guide/#some-more-info) +- Make sure the bot is **not** running. +- `tmux new -s nadeko` (**nadeko** is the name of the session) +- `cd ~ && bash linuxAIO.sh` +- Choose either `1` or `2` to update the bot with **latest build** or **stable build** respectively. +- Choose either `3` or `4` to run the bot again with **normally** or **auto restart** respectively. +- Done. You can close terminal now. + +####Some more Info + +**TMUX** + +- 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 kill NadekoBot session, type `tmux kill-session -t nadeko` + +**Screen** + +- If you want to see the sessions after logging back again, type `screen -ls`, and that will give you the list of screens. +- If you want to switch to/ see that screen, type `screen -r nadeko` (nadeko is the name of the screen we created before so, replace `nadeko` with the screen name you created.) +- If you want to kill the NadekoBot screen, type `screen -X -S nadeko quit` + +####Alternative Method to Install Nadeko + +**METHOD I** + +- `cd ~ && curl -L https://github.com/Kwoth/NadekoBot-BashScript/raw/master/nadeko_installer.sh | sh` + +**METHOD II** + +- `cd ~` +- `git clone -b 1.0 --recursive https://github.com/Kwoth/NadekoBot.git` +- `cd ~/NadekoBot/discord.net` +- `dotnet restore -s https://dotnet.myget.org/F/dotnet-core/api/v3/index.json` +- `dotnet restore` +- `cd ~/NadekoBot/src/NadekoBot/` +- `dotnet restore` +- `dotnet build --configuration Release` [Homebrew]: http://brew.sh/ -[Mono]: http://www.mono-project.com/docs/compiling-mono/mac/ -[Releases]: https://github.com/Kwoth/NadekoBot/releases [DiscordApp]: https://discordapp.com/developers/applications/me [Atom]: https://atom.io/ [Invite Guide]: http://discord.kongslien.net/guide.html diff --git a/docs/guides/Upgrading Guide.md b/docs/guides/Upgrading Guide.md new file mode 100644 index 00000000..cf202e0a --- /dev/null +++ b/docs/guides/Upgrading Guide.md @@ -0,0 +1,9 @@ +###Upgrading Nadeko from an older release + +- Follow the Windows Guide/Linux Guide/OS X Guide linked on the left. +- Navigate to your old `Nadeko` folder and copy `credentials.json` and the `/data/` folder. +- Paste this into the new Nadeko's `/NadekoBot/src/NadekoBot/` folder. +- If it asks you to overwrite files, it is fine to do so. +- Now launch new Nadeko as the guide describes. +- In any channel, run the `.migratedata` command - nadeko will now migrate your old data. +- Restart nadeko and everything should work as expected! diff --git a/docs/guides/Windows Guide.md b/docs/guides/Windows Guide.md index bd3a3eb4..04554aa2 100644 --- a/docs/guides/Windows Guide.md +++ b/docs/guides/Windows Guide.md @@ -1,119 +1,122 @@ -________________________________________________________________________________ -*Thanks to @Flatbread and Mirai for making this guide* -________________________________________________________________________________ - -### Setting Up NadekoBot on Windows -#### Prerequisites -- 1) [NET Framework][NET Framework] 4.5.2 (or 4.6) -- 2) [FFMPEG][FFMPEG] -- 3) Google Account -- 4) Soundcloud Account (if you want soundcloud support) -- 5) [7zip][7zip] (or whatever you are using, WinRar) -- 6) [Notepad++][Notepad++] - -####Guide - -- Create a folder, name it `Nadeko`. -- Head to [Updater Releases Page][Updater] and download `WINDOWS.-.nadeupdater.7z`. -- Copy `WINDOWS.-.nadeupdater.7z` to the `Nadeko` (folder we created before) and extract everything. -- You will see a file `NadekoUpdater.bat ` and a folder `publish ` after extraction. -- Run/Launch/Open the file `NadekoUpdater.bat ` and you will see it running in cmd.exe asking you with **3 options** *1-3*. - - 1) Stable release - current stable release, but might not contain all the newest Nadeko updates. - - 2) Newest release - release with all features/upgrades. - - 3) Exit -- Press `2` on your keyboard and hit `Enter`. Type `y` and hit `Enter` again. Downloading might take a while, so just be patient and wait. When download is done, press `3` on your keyboard and close the updater. -- You should have a new folder named `NadekoBot` inside the `Nadeko` folder we previously created. - -####Creating DiscordBot application -- Go to [DiscordApp][DiscordApp]. -- Log in with your Discord account. -- On the left side, press `New Application`. -- Fill out the `App Name` (your bot's name, in this case), put the image you want, and add an app description(optional). -- Create the application. -- Once the application is created, click on `Create a Bot User` and confirm it. -- Keep this window open for now. - -####Setting up Credentials.json file -- In our `NadekoBot` folder you should have `.json` file named `credentials_example.json`. (Note: If you do not see a **.json** after `credentials_example.json `, do not add the `**.json**`. You most likely have `"Hide file extensions"` enabled.) -- Rename `credentials_example.json` to `credentials.json`. -- Open the file with your [Notepad++][Notepad++]. -- In there you will see fields like `Token`, `ClientId`, `BotId`, `OwnerIDs`. -- In your [DiscordApp][DiscordApp], under `Bot User` part, you will see the `Token:click to reveal` part, click to reveal it. -- Copy your bot's token, and put it between `" "` in your `credentials.json` file. -- Copy `Client ID` and replace it with the example one in your `credentials.json` in `Client ID` **and** `BotID` field. -- Save your `credentials.json` but keep it open. We need to put your `User ID` and owner. - -####Inviting your bot to your server -- [Invite Guide][Invite Guide] -- Create a new server in Discord. -- Copy your `Client ID` from your [DiscordApp][DiscordApp]. -- Replace `12345678` in this link `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with your `Client ID`. -- Link should look like this: `https://discordapp.com/oauth2/authorize?client_id=**YOUR_CLENT_ID**&scope=bot&permissions=66186303`. -- Go to newly created link and pick the server we created, and click `Authorize`. -- Bot should be added to your server. - -####Starting the bot -- Enter your `NadekoBot` folder that should be (hopefully) in your `Nadeko` folder. -- Run `NadekoBot.exe` (Note: There is `NadekoBot.exe` and `NadekoBot.exe.config`, dont run the second one) -- Your bot should now be online in the server we added him to. -- Note: Your bot will be offline in case you close `NadekoBot.exe`. - -####Setting up OwnerIds -- In the server where your bot is, in a text channel, type `.uid` -- Your `User ID` should show, copy it. -- Close `NadekoBot.exe` -- Replace your `User ID` in the `credentials.json` between `[ ]` and save the changes. -- Run `NadekoBot.exe` again. -- Now you are the bot owner. -- You can add `User IDs` from the other users by separating IDs with a comma if you want to have more owners. - -`*Alternatively, you can download nadekobot from` [Releases][Releases] `and extract the zip yourself. -That is what updater does, except it makes it easier for you to update because it doesn't overwrite -important files.If you are downloading releases you will have to be careful about your config, -credentials, and other files you edited in order to preserve your data every time you update.` - -________________________________________________________________________________ - -#### Setting Up NadekoBot For Music -##### Prerequisites -- 1) [FFMPEG][FFMPEG] installed. -- 2) Setting up API keys. - -- Follow these steps on how to setup Google API keys: - - Go to [Google Console][Google Console] and log in. - - Create a new project (name does not matter). Once the project is created, go into "Enable and manage APIs." - - Under the "Other Popular APIs" section, enable `URL Shortener API` and `Custom Search Api`. Under the `YouTube APIs` section, enable `YouTube Data API`. - - On the left tab, access `Credentials`. Click `Create Credentials` button. Click on `API Key`. A new window will appear with your `Google API key`. - - Copy the key. - - Open up `credentials.json`. - - For `"GoogleAPIKey"`, fill in with the new key we copied. -- Follow these steps on how to setup Soundcloud API key: - - Go to [Soundcloud][Soundcloud]. - - Enter a name for the app and create it. - - You will need to fill out an application form to request access to Soundcloud API. - - All requests for an API key must go through the review process, where applications will be reviewed on a case by case basis, in line with Soundcloud API Terms of Use. If your application is successful, you will receive an API key. - - You will see a page with the title of your app, and a field labeled `Client ID`. Copy the ID. - - In `credentials.json`, fill in `"SoundcloudClientID"` with the copied ID. -- Restart your computer. - -####Manual `ffmpeg` setup -**Do this step in case you were not able to install `ffmpeg` with the installer.** - -- Create a folder named `ffmpeg` in your main Windows directory. We will use **C:\ffmpeg** (for our guide) -- Download FFMPEG through the link https://ffmpeg.zeranoe.com/builds/ (download static build) -- Extract it using `7zip` and place the folder `ffmpeg-xxxxx-git-xxxxx-xxxx-static` inside **C:\ffmpeg** -- Before proceeding, check out this gif to set up `ffmpeg` PATH correctly http://i.imgur.com/aR5l1Hn.gif *(thanks to PooPeePants#7135)* -- Go to My Computer, right click and select Properties. On the left tab, select Advanced System Settings. Under the Advanced tab, select Environmental Variables near the bottom. One of the variables should be called "Path". Add a semi-colon (;) to the end followed by your FFMPEG's **bin** install location (**for example C:\ffmpeg\ffmpeg-xxxxx-git-xxxxx-xxxx-static\bin**). Save and close. -- Setup your API keys as explained above. -- Restart your computer. - -[NET Framework]: https://www.microsoft.com/en-us/download/details.aspx?id=48130 -[FFMPEG]: https://github.com/Soundofdarkness/FFMPEG-Inst/releases -[7zip]: http://www.7-zip.org/download.html -[Updater]: https://github.com/Kwoth/NadekoUpdater/releases -[Releases]: https://github.com/Kwoth/NadekoBot/releases -[DiscordApp]: https://discordapp.com/developers/applications/me -[Notepad++]: https://notepad-plus-plus.org/ -[Invite Guide]: http://discord.kongslien.net/guide.html -[Google Console]: https://console.developers.google.com -[Soundcloud]: https://soundcloud.com/you/apps/new +________________________________________________________________________________ +*Thanks to @Flatbread and Mirai for making this guide* +________________________________________________________________________________ + +## Setting Up NadekoBot on Windows + +#### Prerequisites +- 1) [.NET Core SDK][.NET Core SDK] +- 2) [Git][Git] +- 3) [FFMPEG][FFMPEG] +- 4) Google Account +- 5) Soundcloud Account (if you want soundcloud support) +- 6) [7zip][7zip] (or whatever you are using, WinRar) +- 7) [Notepad++][Notepad++] +- 8) Windows 8 or later + +####Guide +- Make sure you have installed both [Git][Git] and the [.NET Core SDK][.NET Core SDK]. +- Create a **new folder** anywhere you like and name it `Nadeko`. +- Next, [Right-Click on this link](https://github.com/Kwoth/NadekoBotInstallerWin/raw/master/NadekoInstaller.bat) and select **Save link as** and save the file `NadekoInstaller.bat` inside the `Nadeko` folder that we created earlier. (**DO NOT** rename the file `NadekoInstaller.bat`) +- Once that's done, double-click on `NadekoInstaller.bat` to run it. +- From the options, + - Choose `1` to get the **most recent build**. + - Choose `2` to get the **stable build**. +- Wait a while for the file to finish installing, it'll display it's progress in the command prompt. +- You should now have a new folder named `NadekoBot` inside the `Nadeko` folder we previously created. +- Once Installation is completed, press any key to close the command prompt. + +####Creating DiscordBot application +- Go to [the Discord developer application page][DiscordApp]. +- Log in with your Discord account. +- On the left side, press `New Application`. +- Fill out the `App Name` (your bot's name, in this case), put the image you want, and add an app description(optional). +- Create the application. +- Click on `Create a Bot User` and confirm that you do want to add a bot to this app. +- Keep this window open for now. + +####Setting up credentials.json file +- In our `NadekoBot` folder you should see a `src` folder, then *another* `NadekoBot` folder, in this final folder, you should see a `.json` file named `credentials.json`. (Note: If you do not see a `.json` after `credentials.json`, do not add the `.json`. You most likely have **"Hide file extensions"** enabled.) +- If you mess up the setup of `credentials.json`, rename `credentials_example.json` to `credentials.json`. +- Open the file with [Notepad++][Notepad++]. +- In there you will see fields such as `Token`, `ClientId`, `BotId` and `OwnerIDs`. +- In your [applications page][DiscordApp] (the window you were asked to keep open earlier), under the `Bot User` section, you will see `Token:click to reveal`, click to reveal the token. +- Copy your bot's token, and on the `"Token"` line of your `credentials.json`, paste your bot token between the quotation marks. +- Copy the `Client ID` on the page and replace the `12312123` part of the `ClientId` line with it. +- Again, copy the same `Client ID` and replace the `null` part of the `BotId` line with it. +- Go to a server on discord and attempt to mention yourself, but put a backslash at the start like shown below +- So the message `\@fearnlj01#3535` will appears as `<@145521851676884992>` after you send the message (to make it slightly easier, add the backslash after you type the mention out) +- The message will appear as a mention if done correctly, copy the numbers from the message you sent (`145521851676884992`) and replace the `0` on the `OwnerIds` section with your user ID shown earlier. +- Save `credentials.json` (make sure you aren't saving it as `credentials.json.txt`) +- If done correctly, you are now the bot owner. You can add multiple owners by seperating each owner ID with a comma within the square brackets. + +####Inviting your bot to your server +- [Invite Guide][Invite Guide] +- Copy your `Client ID` from your [applications page][DiscordApp]. +- Replace the `12345678` in this link `https://discordapp.com/oauth2/authorize?client_id=12345678&scope=bot&permissions=66186303` with your `Client ID`. +- The link should now look like this: `https://discordapp.com/oauth2/authorize?client_id=**YOUR_CLENT_ID_HERE**&scope=bot&permissions=66186303`. +- Go to the newly created link and pick the server we created, and click `Authorize`. +- The bot should have been added to your server. + +####Starting the bot +- Go to the `Nadeko` folder that we have created earlier, and run the `NadekoInstaller.bat` file. +- From the options, + - Choose `3` to **run the bot normally**. + (with normal-run the bot will shutdown and will stay offline if it disconnects by the use of `.die` command until you manually run it again. Useful if you want to test the bot.) + - Choose `4` to **run the bot with auto restart**. + (with auto restart the bot will restart itself if it disconnects by the use of `.die` command. Useful if you want to have restart function for any reason.) + +####Updating NadekoBot +- Make sure the bot is closed and is not running (Run `.die` in a connected server to ensure it's not running). +- Once that's checked, go to the `Nadeko` folder. +- Run the `NadekoInstaller.bat` file. +- From the options, + - Choose `1` to get the **most recent build**. + - Choose `2` to get the **stable build**. +- Follow the messages and press any key to continue. +- Wait for it to finish. +- Press any key to close the window when it shows **Installation complete.** +- [Start the bot again.](http://nadekobot.readthedocs.io/en/latest/guides/Windows%20Guide/#starting-the-bot) +- You've updated and are running again, easy as that! +________________________________________________________________________________ + +#### Setting Up NadekoBot For Music +##### Prerequisites +- 1) [FFMPEG][FFMPEG] installed. +- 2) Setting up API keys. + +- Follow these steps on how to setup Google API keys: + - Go to [Google Console][Google Console] and log in. + - Create a new project (name does not matter). Once the project is created, go into "Enable and manage APIs." + - Under the "Other Popular APIs" section, enable `URL Shortener API` and `Custom Search Api`. Under the `YouTube APIs` section, enable `YouTube Data API`. + - On the left tab, access `Credentials`. Click `Create Credentials` button. Click on `API Key`. A new window will appear with your `Google API key`. + - Copy the key. + - Open up `credentials.json`. + - For `"GoogleAPIKey"`, paste your API key inbetween the quotation marks, like how the `Token` and `ClientId` should be set up. +- Follow these steps on how to setup Soundcloud API key: + - Go to [Soundcloud][Soundcloud]. + - Enter a name for the app and create it. + - You will need to fill out an application form to request access to the Soundcloud API. + - All requests for an API key must go through the review process, where applications will be reviewed on a case by case basis, in line with Soundcloud API Terms of Use. If your application is successful, you will receive an API key. +- **Restart your computer**. + +####Manual `ffmpeg` setup +**Do this step in case you were not able to install `ffmpeg` with the installer.** + +- Create a folder named `ffmpeg` in your main Windows directory. We will use **C:\ffmpeg** (for our guide) +- Download FFMPEG through the link https://ffmpeg.zeranoe.com/builds/ (download static build) +- Extract it using `7zip` and place the folder `ffmpeg-xxxxx-git-xxxxx-xxxx-static` inside **C:\ffmpeg** +- Before proceeding, check out this gif to set up `ffmpeg` PATH correctly ![LINK TO gif](http://i.imgur.com/aR5l1Hn.gif) *(thanks to PooPeePants#7135)* +- Go to My Computer, right click and select Properties. On the left tab, select Advanced System Settings. Under the Advanced tab, select Environmental Variables near the bottom. One of the variables should be called "Path". Add a semi-colon (;) to the end followed by your FFMPEG's **bin** install location (**for example C:\ffmpeg\ffmpeg-xxxxx-git-xxxxx-xxxx-static\bin**). Save and close. +- Setup your API keys as explained above. +- Restart your computer. + +[.NET Core SDK]: https://www.microsoft.com/net/core#windowscmd +[Git]: https://git-scm.com/download/win +[FFMPEG]: https://github.com/Soundofdarkness/FFMPEG-Inst/releases +[7zip]: http://www.7-zip.org/download.html +[DiscordApp]: https://discordapp.com/developers/applications/me +[Notepad++]: https://notepad-plus-plus.org/ +[Invite Guide]: http://discord.kongslien.net/guide.html +[Google Console]: https://console.developers.google.com +[Soundcloud]: https://soundcloud.com/you/apps/new diff --git a/docs/index.md b/docs/index.md index 84cdb82e..872e2e40 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,47 +1,47 @@ -#NadekoBot Documentation - -To invite NadekoBot to your server, click on the image bellow: - -[![img][img]](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) - -In case you need any help, hop on [NadekoBot Server][NadekoBot Server], where we can provide support. - -NadekoBot is an open source project, and it can be found on our [GitHub][GitHub] page. - -Here you can read current [Issues][Issues]. - -If you want to contribute, be sure to PR on the **[dev][dev]** branch. -##Content -- [About](about.md) -- Guides - - [Windows Guide](guides/Windows Guide.md) - - [Linux Guide](guides/Linux Guide.md) - - [OSX Guide](guides/OSX Guide.md) - - [Building from Source](guides/Building from Source.md) - - [Docker Guide](guides/Docker Guide.md) -- Commands - - [Readme](Readme.md) - - [Commands List](Commands List.md) -- [Permissions System](Permissions System.md) -- [JSON Explanations](JSON Explanations.md) -- [Custom Reactions](Custom Reactions.md) -- [Frequently Asked Questions](Frequently Asked Questions.md) -- [Contribution Guide](Contribution Guide.md) -- [Donate](Donate.md) - -[img]: https://discordcdn.com/attachments/202743183774318593/210580315381563392/discord.png -[NadekoBot Server]: https://discord.gg/0ehQwTK2RBjAxzEY -[GitHub]: https://github.com/Kwoth/NadekoBot -[Issues]: https://github.com/Kwoth/NadekoBot/issues -[dev]: https://github.com/Kwoth/NadekoBot/tree/dev - -[Italian]: http://i.imgur.com/SsaTwOF.png?1 -[Russian]: http://i.imgur.com/wf9bc5G.png?1 -[German]: http://i.imgur.com/EM5qPzf.png?1 -[Chinese]: http://i.imgur.com/MVCNOjT.png?1 -[English]: http://i.imgur.com/jHTyZFS.png?1 -[Spanish]: http://i.imgur.com/9BsusB6.png?1 -[French]: http://i.imgur.com/g2ARPF6.png?1 -[Dutch]: http://i.imgur.com/SadddLj.png?1 -[Norwegian]: http://i.imgur.com/TCVa0V8.png?1 -[Serbian]: http://i.imgur.com/5evoUbU.png +#NadekoBot Documentation + +To invite NadekoBot to your server, click on the image bellow: + +[![img][img]](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) + +In case you need any help, hop on the [NadekoBot Server][NadekoBot Server], where we can provide support. + +NadekoBot is an open source project, and it can be found on our [GitHub][GitHub] page. + +Here you can read current [Issues][Issues]. + +If you want to contribute, be sure to PR on the **[dev][dev]** branch. +##Content +- [About](about.md) +- Guides + - [Windows Guide](guides/Windows Guide.md) + - [Linux Guide](guides/Linux Guide.md) + - [OSX Guide](guides/OSX Guide.md) + - [Docker Guide](guides/Docker Guide.md) + - [Upgrading Guide](guides/Upgrading Guide.md) +- Commands + - [Readme](Readme.md) + - [Commands List](Commands List.md) +- [Permissions System](Permissions System.md) +- [JSON Explanations](JSON Explanations.md) +- [Custom Reactions](Custom Reactions.md) +- [Frequently Asked Questions](Frequently Asked Questions.md) +- [Contribution Guide](Contribution Guide.md) +- [Donate](Donate.md) + +[img]: https://cdn.discordapp.com/attachments/202743183774318593/210580315381563392/discord.png +[NadekoBot Server]: https://discord.gg/0ehQwTK2RBjAxzEY +[GitHub]: https://github.com/Kwoth/NadekoBot +[Issues]: https://github.com/Kwoth/NadekoBot/issues +[dev]: https://github.com/Kwoth/NadekoBot/tree/dev + +[Italian]: http://i.imgur.com/SsaTwOF.png?1 +[Russian]: http://i.imgur.com/wf9bc5G.png?1 +[German]: http://i.imgur.com/EM5qPzf.png?1 +[Chinese]: http://i.imgur.com/MVCNOjT.png?1 +[English]: http://i.imgur.com/jHTyZFS.png?1 +[Spanish]: http://i.imgur.com/9BsusB6.png?1 +[French]: http://i.imgur.com/g2ARPF6.png?1 +[Dutch]: http://i.imgur.com/SadddLj.png?1 +[Norwegian]: http://i.imgur.com/TCVa0V8.png?1 +[Serbian]: http://i.imgur.com/5evoUbU.png diff --git a/ffmpeg-installer b/ffmpeg-installer deleted file mode 160000 index d593fe3a..00000000 --- a/ffmpeg-installer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d593fe3a86be7da9e4177865446f2f5ca58b6be4 diff --git a/global.json b/global.json new file mode 100644 index 00000000..9c989b51 --- /dev/null +++ b/global.json @@ -0,0 +1,3 @@ +{ + "projects": [ "discord.net/src", "src" ] +} diff --git a/license.md b/license.md index ad9c4e5f..68a49daa 100644 --- a/license.md +++ b/license.md @@ -1,19 +1,24 @@ -Copyright (c) 2015 Master Kwoth +This is free and unencumbered software released into the public domain. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/mkdocs.yml b/mkdocs.yml index 0028001f..2e42b673 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,22 +1,22 @@ -site_name: NadekoBot -repo_url: https://github.com/Kwoth/NadekoBot -pages: -- Home: index.md -- About: about.md -- Guides: - - Windows Guide: guides/Windows Guide.md - - Linux Guide: guides/Linux Guide.md - - OSX Guide: guides/OSX Guide.md - - Building from Source: guides/Building from Source.md - - Docker Guide: guides/Docker Guide.md -- Commands: - - Readme: Readme.md - - Commands List: Commands List.md -- Permissions System: Permissions System.md -- JSON Explanations: JSON Explanations.md -- Custom Reactions: Custom Reactions.md -- Frequently Asked Questions: Frequently Asked Questions.md -- Contribution Guide: Contribution Guide.md -- ❤ Donate ❤: Donate.md - -theme: readthedocs +site_name: NadekoBot +repo_url: https://github.com/Kwoth/NadekoBot +pages: +- Home: index.md +- About: about.md +- Guides: + - Windows Guide: guides/Windows Guide.md + - Linux Guide: guides/Linux Guide.md + - OSX Guide: guides/OSX Guide.md + - Docker Guide: guides/Docker Guide.md + - Upgrading Guide: guides/Upgrading Guide.md +- Commands: + - Readme: Readme.md + - Commands List: Commands List.md +- Permissions System: Permissions System.md +- JSON Explanations: JSON Explanations.md +- Custom Reactions: Custom Reactions.md +- Frequently Asked Questions: Frequently Asked Questions.md +- Contribution Guide: Contribution Guide.md +- ❤ Donate ❤: Donate.md + +theme: readthedocs diff --git a/scripts/Latest.bat b/scripts/Latest.bat new file mode 100644 index 00000000..8256adbc --- /dev/null +++ b/scripts/Latest.bat @@ -0,0 +1,110 @@ +@ECHO off +TITLE Downloading NadekoBot, please wait +::Setting convenient to read variables which don't delete the windows temp folder +SET root=%~dp0 +CD /D %root% +SET rootdir=%cd% +SET build1=%root%NadekoInstall_Temp\NadekoBot\discord.net\src\Discord.Net\ +SET build2=%root%NadekoInstall_Temp\NadekoBot\discord.net\src\Discord.Net.Commands\ +SET build3=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\ +SET installtemp=%root%NadekoInstall_Temp\ +::Deleting traces of last setup for the sake of clean folders, if by some miracle it still exists +IF EXIST %installtemp% ( RMDIR %installtemp% /S /Q >nul 2>&1) +::Checks that both git and dotnet are installed +dotnet --version >nul 2>&1 || GOTO :dotnet +git --version >nul 2>&1 || GOTO :git +::Creates the install directory to work in and get the current directory because spaces ruins everything otherwise +:start +MKDIR NadekoInstall_Temp +CD /D %installtemp% +::Downloads the latest version of Nadeko +ECHO Downloading Nadeko... +ECHO. +git clone -b dev --recursive --depth 1 --progress https://github.com/Kwoth/NadekoBot.git >nul +IF %ERRORLEVEL% EQU 128 (GOTO :giterror) +TITLE Installing NadekoBot, please wait +ECHO. +ECHO Installing... +::Building Nadeko +CD /D %build1% +dotnet restore >nul 2>&1 +CD /D %build2% +dotnet restore >nul 2>&1 +CD /D %build3% +dotnet restore >nul 2>&1 +dotnet build --configuration Release >nul 2>&1 +::Attempts to backup old files if they currently exist in the same folder as the batch file +IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) +:freshinstall + ::Moves the NadekoBot folder to keep things tidy + ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + GOTO :end +:backupinstall + TITLE Backing up old files + ECHO. + ECHO Make sure to close any files such as NadekoBot.db before PRESSing ANY KEY TO CONTINUE to prevent data loss + PAUSE >nul 2>&1 + ::Recursively copies all files and folders from NadekoBot to NadekoBot_Old + ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + ECHO. + ECHO Old files backed up to NadekoBot_Old + ::Copies the credentials and database from the backed up data to the new folder + COPY "%root%NadekoBot_Old\src\NadekoBot\credentials.json" "%installtemp%NadekoBot\src\NadekoBot\credentials.json" >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + ECHO. + ECHO credentials.json copied to new folder + ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\bin" "%installtemp%NadekoBot\src\NadekoBot\bin" /E >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + ECHO. + ECHO Old bin folder copied to new folder + ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\data" "%installtemp%NadekoBot\src\NadekoBot\data" /E >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + ECHO. + ECHO Old data folder copied to new folder + ::Moves the setup Nadeko folder + RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1 + ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + GOTO :end +:dotnet + ::Terminates the batch script if it can't run dotnet --version + TITLE Error! + ECHO dotnet not found, make sure it's been installed as per the guides instructions! + ECHO Press any key to exit. + PAUSE >nul 2>&1 + CD /D "%root%" + GOTO :EOF +:git + ::Terminates the batch script if it can't run git --version + TITLE Error! + ECHO git not found, make sure it's been installed as per the guides instructions! + ECHO Press any key to exit. + PAUSE >nul 2>&1 + CD /D "%root%" + GOTO :EOF +:giterror + ECHO. + ECHO Git clone failed, trying again + RMDIR %installtemp% /S /Q >nul 2>&1 + GOTO :start +:copyerror + ::If at any point a copy error is encountered + TITLE Error! + ECHO. + ECHO An error in copying data has been encountered, returning an exit code of %ERRORLEVEL% + ECHO. + ECHO Make sure to close any files, such as `NadekoBot.db` before continuing or try running the installer as an Administrator + PAUSE >nul 2>&1 + CD /D "%root%" + GOTO :EOF +:end + ::Normal execution of end of script + TITLE Installation complete! + CD /D "%root%" + RMDIR /S /Q "%installtemp%" >nul 2>&1 + ECHO. + ECHO Installation complete, press any key to close this window! + PAUSE >nul 2>&1 + del Latest.bat diff --git a/scripts/NadekoAutoRun.bat b/scripts/NadekoAutoRun.bat new file mode 100644 index 00000000..af1b87d5 --- /dev/null +++ b/scripts/NadekoAutoRun.bat @@ -0,0 +1,6 @@ +@ECHO off +@TITLE NadekoBot +:auto +CD /D %~dp0NadekoBot\src\NadekoBot +dotnet run --configuration Release +goto auto diff --git a/scripts/NadekoRun.bat b/scripts/NadekoRun.bat new file mode 100644 index 00000000..207fb278 --- /dev/null +++ b/scripts/NadekoRun.bat @@ -0,0 +1,9 @@ +@ECHO off +@TITLE NadekoBot +CD /D %~dp0NadekoBot\src\NadekoBot +dotnet run --configuration Release +ECHO NadekoBot has been succesfully stopped, press any key to close this window. +TITLE NadekoBot - Stopped +CD /D %~dp0 +PAUSE >nul 2>&1 +del NadekoRunNormal.bat diff --git a/scripts/Stable.bat b/scripts/Stable.bat new file mode 100644 index 00000000..f617ac8b --- /dev/null +++ b/scripts/Stable.bat @@ -0,0 +1,110 @@ +@ECHO off +TITLE Downloading NadekoBot, please wait +::Setting convenient to read variables which don't delete the windows temp folder +SET root=%~dp0 +CD /D %root% +SET rootdir=%cd% +SET build1=%root%NadekoInstall_Temp\NadekoBot\discord.net\src\Discord.Net\ +SET build2=%root%NadekoInstall_Temp\NadekoBot\discord.net\src\Discord.Net.Commands\ +SET build3=%root%NadekoInstall_Temp\NadekoBot\src\NadekoBot\ +SET installtemp=%root%NadekoInstall_Temp\ +::Deleting traces of last setup for the sake of clean folders, if by some miracle it still exists +IF EXIST %installtemp% ( RMDIR %installtemp% /S /Q >nul 2>&1) +::Checks that both git and dotnet are installed +dotnet --version >nul 2>&1 || GOTO :dotnet +git --version >nul 2>&1 || GOTO :git +::Creates the install directory to work in and get the current directory because spaces ruins everything otherwise +:start +MKDIR NadekoInstall_Temp +CD /D %installtemp% +::Downloads the latest version of Nadeko +ECHO Downloading Nadeko... +ECHO. +git clone -b master --recursive --depth 1 --progress https://github.com/Kwoth/NadekoBot.git >nul +IF %ERRORLEVEL% EQU 128 (GOTO :giterror) +TITLE Installing NadekoBot, please wait +ECHO. +ECHO Installing... +::Building Nadeko +CD /D %build1% +dotnet restore >nul 2>&1 +CD /D %build2% +dotnet restore >nul 2>&1 +CD /D %build3% +dotnet restore >nul 2>&1 +dotnet build --configuration Release >nul 2>&1 +::Attempts to backup old files if they currently exist in the same folder as the batch file +IF EXIST "%root%NadekoBot\" (GOTO :backupinstall) +:freshinstall + ::Moves the NadekoBot folder to keep things tidy + ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + GOTO :end +:backupinstall + TITLE Backing up old files + ECHO. + ECHO Make sure to close any files such as NadekoBot.db before PRESSing ANY KEY TO CONTINUE to prevent data loss + PAUSE >nul 2>&1 + ::Recursively copies all files and folders from NadekoBot to NadekoBot_Old + ROBOCOPY "%root%NadekoBot" "%root%NadekoBot_Old" /MIR >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + ECHO. + ECHO Old files backed up to NadekoBot_Old + ::Copies the credentials and database from the backed up data to the new folder + COPY "%root%NadekoBot_Old\src\NadekoBot\credentials.json" "%installtemp%NadekoBot\src\NadekoBot\credentials.json" >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + ECHO. + ECHO credentials.json copied to new folder + ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\bin" "%installtemp%NadekoBot\src\NadekoBot\bin" /E >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + ECHO. + ECHO Old bin folder copied to new folder + ROBOCOPY "%root%NadekoBot_Old\src\NadekoBot\data" "%installtemp%NadekoBot\src\NadekoBot\data" /E >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + ECHO. + ECHO Old data folder copied to new folder + ::Moves the setup Nadeko folder + RMDIR "%root%NadekoBot\" /S /Q >nul 2>&1 + ROBOCOPY "%root%NadekoInstall_Temp" "%rootdir%" /E /MOVE >nul 2>&1 + IF %ERRORLEVEL% GEQ 8 (GOTO :copyerror) + GOTO :end +:dotnet + ::Terminates the batch script if it can't run dotnet --version + TITLE Error! + ECHO dotnet not found, make sure it's been installed as per the guides instructions! + ECHO Press any key to exit. + PAUSE >nul 2>&1 + CD /D "%root%" + GOTO :EOF +:git + ::Terminates the batch script if it can't run git --version + TITLE Error! + ECHO git not found, make sure it's been installed as per the guides instructions! + ECHO Press any key to exit. + PAUSE >nul 2>&1 + CD /D "%root%" + GOTO :EOF +:giterror + ECHO. + ECHO Git clone failed, trying again + RMDIR %installtemp% /S /Q >nul 2>&1 + GOTO :start +:copyerror + ::If at any point a copy error is encountered + TITLE Error! + ECHO. + ECHO An error in copying data has been encountered, returning an exit code of %ERRORLEVEL% + ECHO. + ECHO Make sure to close any files, such as `NadekoBot.db` before continuing or try running the installer as an Administrator + PAUSE >nul 2>&1 + CD /D "%root%" + GOTO :EOF +:end + ::Normal execution of end of script + TITLE Installation complete! + CD /D "%root%" + RMDIR /S /Q "%installtemp%" >nul 2>&1 + ECHO. + ECHO Installation complete, press any key to close this window! + PAUSE >nul 2>&1 + del Stable.bat diff --git a/scripts/psychology-10.json b/scripts/psychology-10.json deleted file mode 100644 index 365e2066..00000000 --- a/scripts/psychology-10.json +++ /dev/null @@ -1,364 +0,0 @@ -[ - { - "Title":"The Gender of Psychology", - "Description":"This book addresses the diversity of psychological knowledge and practice through the lens of gender." - }, - { - "Title":"Unto Others: The Evolution and Psychology of Unselfish", - "Description":"In Unto Others philosopher Elliott Sober and biologist David Sloan Wilson demonstrate once and for all that unselfish behavior is in fact an important feature of both biological and human nature." - }, - { - "Title":"Forensic and Legal Psychology", - "Description":"Using research in clinical, cognitive, developmental, and social psychology, Forensic and Legal Psychology shows how psychological science can enhance the gathering and presentation of evidence, improve legal decision-making, prevent crime," - }, - { - "Title":"International Handbook of Psychology in Education", - "Description":"Suitable for researchers, practitioners and advisers working in the fields of psychology and education, this title presents an overview of the research within the domain of psychology of education." - }, - { - "Title":"Handbook of Personality Psychology", - "Description":"This comprehensive reference work on personality psychology discusses the development and measurement of personality, biological and social determinants, dynamic personality processes, the personality's relation to the self, and personality" - }, - { - "Title":"Dictionary of Theories, Laws, and Concepts in Psychology", - "Description":"A fully cross-referenced and source-referenced dictionary which gives definitions of psychological terms as well as the history, critique, and relevant references for the terms." - }, - { - "Title":"Essays on Plato's Psychology", - "Description":"With a comprehensive introduction to the major issues of Plato's psychology and an up-to-date bibliography of work on the relevant issues, this much-needed text makes the study of Plato's psychology accessible to scholars in ancient Greek" - }, - { - "Title":"Psychology Statistics For Dummies", - "Description":"As an alternative to typical, lead-heavy statistics texts or supplements to assigned course reading, this is one book psychology students won't want to be without." - }, - { - "Title":"Doing Psychology Experiments", - "Description":"David W. Martin’s unique blend of informality, humor, clear instruction, and solid scholarship make this concise text a popular choice for research methods courses in psychology." - }, - { - "Title":"A Handbook of Research Methods for Clinical and Health", - "Description":"For both undergraduate and postgraduate students, the book will be essential in making them aware of the full range of techniques available to them, helping them to design scientifically rigorous experiments." - }, - { - "Title":"A History of Psychology", - "Description":"First published in 2002. Routledge is an imprint of Taylor & Francis, an informa company." - }, - { - "Title":"An Introduction to the Psychology of Religion", - "Description":"The third edition of this successful book, which applies the science of psychology to problems of religion. Dr Thouless explores such questions as: why do people believe? Why are their beliefs often held with irrational strength?" - }, - { - "Title":"Psychology of Champions: How to Win at Sports and Life", - "Description":"In this unprecedented book, two psychologist researchers interview sports legends and super-athletes across sports to explain the thinking that powers stellar performers, pushing them to amazing and historic successes." - }, - { - "Title":"The Psychology of Humor: An Integrative Approach", - "Description":"This is a singly authored monograph that provides in one source, a summary of information researchers might wish to know about research into the psychology of humor." - }, - { - "Title":"Psychology and Deterrence", - "Description":"Now available in paperback, Psychology and Deterrence reveals deterrence strategy's hidden and generally simplistic assumptions about the nature of power and aggression, threat and response, and calculation and behavior in the international" - }, - { - "Title":"Psychology: An International Perspective", - "Description":"Unlike typical American texts, this book provides an international approach to introductory psychology, providing comprehensive and lively coverage of current research from a global perspective, including the UK, Germany, Scandinavia," - }, - { - "Title":"Psychology, Briefer Course", - "Description":"Despite its title, 'Psychology: Briefer Course' is more than a simple condensation of the great 'Principles of Psychology." - }, - { - "Title":"Psychology, Seventh Edition (High School)", - "Description":"This new edition continues the story of psychology with added research and enhanced content from the most dynamic areas of the field—cognition, gender and diversity studies, neuroscience and more, while at the same time using the most" - }, - { - "Title":"Psychology of Russia: Past, Present, Future", - "Description":"This book is for all psychologists and for readers whose interest in Russia exceeds their interest in psychology. Readers of this book will quickly discover a new world of thought." - }, - { - "Title":"Barron's AP Psychology", - "Description":"Provides information on scoring and structure of the test, offers tips on test-taking strategies, and includes practice examinations and subject review." - }, - { - "Title":"Psychology for Inclusive Education: New Directions in", - "Description":"International in focus and at the very cutting edge of the field, this is essential reading for all those interested in the development of inclusive education." - }, - { - "Title":"Applied Psychology: Putting Theory Into Practice", - "Description":"Applied Psychology: Putting theory into practice demonstrates how psychology theory is applied in the real world." - }, - { - "Title":"The Psychology of Science: A Reconnaissance", - "Description":"' This eBook edition contains the complete 168 page text of the original 1966 hardcover edition. Contents: Preface by Abraham H. Maslow Acknowledgments 1. Mechanistic and Humanistic Science 2." - }, - { - "Title":"Filipino American Psychology: A Handbook of Theory,", - "Description":"This book is the first of its kind and aims to promote visibility of this invisible group, so that 2.4 million Filipino Americans will have their voices heard." - }, - { - "Title":"The Psychology of Visual Illusion", - "Description":"Well-rounded perspective on the ambiguities of visual display emphasizes geometrical optical illusions: framing and contrast effects, distortion of angles and direction, and apparent 'movement' of images. 240 drawings. 1972 edition." - }, - { - "Title":"The Psychology of Women", - "Description":"This highly respected text offers students an enjoyable, extraordinarily well-written introduction to the psychology of women with an up-to-date examination of the field and comprehensive coverage of topics." - }, -, - { - "Title":"Psychology and Race", - "Description":"' Psychology and Race is divided into two major parts. The first half of the book looks at the interracial situation itself." - }, - { - "Title":"Psychology for A-Level", - "Description":"'Precisely targeted at AQA A Level Psychology, specification A. It will also be of interest to those who are new to psychology, and who want to get a flavour of the kinds of topics in which psychologists are interested'--Preface, p. vii." - }, - { - "Title":"Biological Psychology", - "Description":"Updated with new topics, examples, and recent research findings--and supported by new online bio-labs, part of the strongest media package yet--this text speaks to today’s students and instructors." - }, - { - "Title":"Psychology: Concepts & Connections", - "Description":"The theme of this book is applying theories and research to learning and to contemporary life." - }, - { - "Title":"The Psychology of Adoption", - "Description":"In this volume David Brodzinsky, who has conducted one of the nation's largest studies of adopted children, and Marshall Schechter, a noted child psychiatrist who has been involved with adoption related issues for over forty years, have" - }, - { - "Title":"Psychology and Adult Learning", - "Description":"This new edition is thoroughly revised and updated in light of the impact of globalising processes and the application of new information technologies, and the influence of postmodernism on psychology." - }, - { - "Title":"Gestalt Psychology: An Introduction to New Concepts in", - "Description":"The general reader, if he looks to psychology for something more than entertainment or practical advice, will discover in this book a storehouse of searching criticism and brilliant suggestions from the pen of a rare thinker, and one who" - }, - { - "Title":"The Psychology of Goals", - "Description":"Bringing together leading authorities, this tightly edited volume reviews the breadth of current knowledge about goals and their key role in human behavior." - }, - { - "Title":"Metaphors in the History of Psychology", - "Description":"Through the identification of these metaphors, the contributors to this volume have provided a remarkably useful guide to the history, current orientations, and future prospects of modern psychology." - }, - { - "Title":"Abnormal Psychology: An Integrative Approach", - "Description":"ABNORMAL PSYCHOLOGY: AN INTEGRATIVE APPROACH, Seventh Edition, is the perfect book to help you succeed in your abnormal psychology course!" - }, - { - "Title":"Art and Visual Perception: A Psychology of the Creative Eye", - "Description":"Gestalt theory and the psychology of visual perception form the basis for an analysis of art and its basic elements" - }, - { - "Title":"Psychology & Christianity: Five Views", - "Description":"This revised edition of a widely appreciated text now presents five models for understanding the relationship between psychology and Christianity." - }, - { - "Title":"The Psychology of Hope: You Can Get There from Here", - "Description":"Why do some people lead positive, hope-filled lives, while others wallow in pessimism? In The Psychology of Hope, a professor of psychology reveals the specific character traits that produce highly hopeful individuals." - }, - { - "Title":"Perspectives on Psychology", - "Description":"This is a title in the modular 'Principles in Psychology Series', designed for A-level and other introductory courses, aiming to provide students embarking on psychology courses with the necessary background and context." - }, - { - "Title":"Psychology the Easy Way", - "Description":"Material is presented in a way that makes these books ideal as self-teaching guides, but Easy Way titles are also preferred by many teachers as supplements to classroom textbooks." - }, - { - "Title":"Ethics in Psychology: Professional Standards and Cases", - "Description":"In this book, their main intent is to present the full range of contemporary ethical issues in psychology as not only relevant and intriguing, but also as integral and unavoidable aspects of the profession." - }, - { - "Title":"Psychology Gets in the Game: Sport, Mind, and Behavior,", - "Description":"The essays collected in this volume tell the stories not only of these psychologists and their subjects but of the social and academic context that surrounded them, shaping and being shaped by their ideas'--Provided by publisher." - }, - { - "Title":"Psychology for Physical Educators: Student in Focus", - "Description":"This updated edition focuses on attitude and motivation as important aspects of the physical education curriculum, illustrating practical ideas and pedagogical solutions for any PE setting." - }, - { - "Title":"The Psychology of Leadership: New Perspectives and Research", - "Description":"In this book, some of the world's leading scholars come together to describe their thinking and research on the topic of the psychology of leadership." - }, - { - "Title":"The Psychology of Interpersonal Relations", - "Description":"As the title suggests, this book examines the psychology of interpersonal relations. In the context of this book, the term 'interpersonal relations' denotes relations between a few, usually between two, people." - }, - { - "Title":"Applied Psychology", - "Description":"The chapters on Counselling Psychology and Teaching Psychology are available online via the Student Companion Site at: http://tinyurl.com/c3ztvtj The text is written to be accessible to Level 1 Introductory Psychology students, and also to" - }, - { - "Title":"Psychology", - "Description":"An exciting read for anyone interested in psychology and research; because of its comprehensive appendix, glossary, and reference section, this book is a must-have desk reference for psychologists and others in the field." - }, - { - "Title":"The Psychology of Music", - "Description":"On interpreting musical phenomena in terms of mental function" - }, - { - "Title":"Abnormal Psychology", - "Description":"Ron Comer's Abnormal Psychology continues to captivate students with its integrated coverage of theory, diagnosis, and treatment, its inclusive wide-ranging cross-cultural perspective, and its compassionate emphasis on the real impact of" - }, - { - "Title":"The Psychology of Food Choice", - "Description":"This book brings together theory, research and applications from psychology and behavioural sciences applied to dietary behaviour." - }, - { - "Title":"Psychology: brain, behavior, & culture", - "Description":"Rather than present psychological science as a series of facts for memorization, this book takes readers on a psychological journey that uncovers things they didn't know or new ways of thinking about things they did know." - }, - { - "Title":"A Brief History of Psychology", - "Description":"Due to its brevity and engaging style, the book is often used in introductory courses to introduce students to the field. The enormous index and substantial glossary make this volume a useful desk reference for the entire field." - }, - { - "Title":"Psychology AS: The Complete Companion", - "Description":"Presented in double-page spreads this book written to the average AS ability level, provides information on psychology in bite-sized chunks with learning and revision features." - }, - { - "Title":"The Psychology Book: From Shamanism to Cutting-Edge", - "Description":"Lavishly illustrated, this new addition in the Sterling's Milestones series chronicles the history of psychology through 250 groundbreaking events, theories, publications, experiments and discoveries." - }, - { - "Title":"The Psychology Book", - "Description":"All the big ideas, simply explained - an innovative and accessible guide to the study of human nature The Psychology Book clearly explains more than 100 groundbreaking ideas in this fascinating field of science." - }, - { - "Title":"Handbook of Positive Psychology", - "Description":"' The Handbook of Positive Psychology provides a forum for a more positive view of the human condition. In its pages, readers are treated to an analysis of what the foremost experts believe to be the fundamental strengths of humankind." - }, - { - "Title":"Psychology of Sustainable Development", - "Description":"With contributions from an international team of policy shapers and makers, the book will be an important reference for environmental, developmental, social, and organizational psychologists, in addition to other social scientists concerned" - }, - { - "Title":"An Introduction to the History of Psychology", - "Description":"In this Fifth Edition, B.R. Hergenhahn demonstrates that most of the concerns of contemporary psychologists are manifestations of themes that have been part of psychology for hundreds-or even thousands-of years." - }, - { - "Title":"Careers in Psychology: Opportunities in a Changing World", - "Description":"This text addresses the growing need among students and faculty for information about the careers available in psychology at the bachelor’s and graduate level." - }, - { - "Title":"Philosophy of Psychology", - "Description":"This is the story of the clattering of elevated subways and the cacophony of crowded neighborhoods, the heady optimism of industrial progress and the despair of economic recession, and the vibrancy of ethnic cultures and the resilience of" - }, - { - "Title":"The Psychology of Risk Taking Behavior", - "Description":"This book aims to help the reader to understand what motivates people to engage in risk taking behavior, such as participating in traffic, sports, financial investments, or courtship." - }, - { - "Title":"The Nazi Doctors: Medical Killing and the Psychology of", - "Description":"This book explores the psychological conditions that promote the human potential for evil, relating medical killing to broader principles of doubling and genocide" - }, - { - "Title":"The Body and Psychology", - "Description":"The material in this volume was previously published as a Special Issue of th" - }, -, - { - "Title":"Introduction to Psychology: Gateways to Mind and Behavior", - "Description":"Important Notice: Media content referenced within the product description or the product text may not be available in the ebook version." - }, - { - "Title":"Psychology of Time", - "Description":"Basic Structure The book would contain 14 or 15 chapters of roughly 12 000 words. The exact final number of chapters would depend on further discussions with you about the book's basic structure." - }, - { - "Title":"Handbook of Psychology, Experimental Psychology", - "Description":"Includes established theories and cutting-edge developments. Presents the work of an international group of experts. Presents the nature, origin, implications, and future course of major unresolved issues in the area." - }, - { - "Title":"Study Guide for Psychology, Seventh Edition", - "Description":"This new edition continues the story of psychology with added research and enhanced content from the most dynamic areas of the field--cognition, gender and diversity studies, neuroscience and more, while at the same time using the most" - }, - { - "Title":"Culture and Psychology", - "Description":"In addition, the text encourages students to question traditionally held beliefs and theories as and their relevance to different cultural groups today." - }, - { - "Title":"Exploring the Psychology of Interest", - "Description":"The most comprehensive work of its kind, Exploring the Psychology of Interest will be a valuable resource for student and professional researchers in cognitive, social, and developmental psychology." - }, - { - "Title":"Handbook of Adolescent Psychology", - "Description":"The study of adolescence in the field of psychology has grown tremendously over the last two decades, necessitating a comprehensive and up-to-date revision of this seminal work." - }, -, - { - "Title":"The Psychology of Diplomacy", - "Description":"World class clinicians, researchers, and activists present the psychological dimensions to diplomacy drawn from examples set in the United Nations, Camp David, the Middle East, Japan, South Africa, and elsewhere." - }, - { - "Title":"The Psychology of Social Class", - "Description":"By addressing differences in social class, the book broadens the perspective of social psychological research to examine such topics as the effect of achievement motivation and other personality variables on social mobility and the effect" - }, - { - "Title":"Applied Psychology: Current Issues and New Directions", - "Description":"Key features of this book: - Consistently pedagogical throughout - chapter summaries, questions for reflection and discussion and annotated further reading in every chapter - Comprehensive coverage - all areas of applied psychology included" - }, - { - "Title":"Popular Psychology: An Encyclopedia", - "Description":"Entries cover a variety of topics in the field of popular psychology, including acupuncture, emotional intelligence, brainwashing, chemical inbalance, and seasonal affective disorder." - }, - { - "Title":"Advanced Psychology: Applications, Issues and Perspectives", - "Description":"The second of two books, Advanced Psychology covers units 4 to 6 for the second year at Advanced Level." - }, - { - "Title":"Mindset: The New Psychology of Success", - "Description":"This is a book that can change your life, as its ideas have changed mine.”—Robert J. Sternberg, IBM Professor of Education and Psychology at Yale University, director of the PACE Center of Yale University, and author of Successful" - }, - { - "Title":"E-Z Psychology", - "Description":"This book covers material as it is taught on a college-101 level." - }, - { - "Title":"Myers' Psychology for AP*", - "Description":"Already The Bestselling AP* Psychology Author, Myers Writes His First Exclusive AP* Psych Text Watch Dave G. Myers introduce this new text here." - }, - { - "Title":"Psychology and Health", - "Description":"Part of a series of textbooks which have been written to support A levels in psychology. The books use real life applications to make theories come alive for students and teach them what they need to know." - }, - { - "Title":"Applying Psychology in Business: The Handbook for Managers", - "Description":"To learn more about Rowman & Littlefield titles please visit us at www.rowmanlittlefield.com." - }, - { - "Title":"Influence", - "Description":"Influence, the classic book on persuasion, explains the psychology of why people say 'yes'—and how to apply these understandings. Dr. Robert Cialdini is the seminal expert in the rapidly expanding field of influence and persuasion." - }, - { - "Title":"Psychology and Policing", - "Description":"The book should draw attention to the often unrecognized and valuable contribution that mainstream psychology can make to the knowledge base underpinning a wide variety of policing practices." - }, - { - "Title":"Applied Psychology: New Frontiers and Rewarding Careers", - "Description":"This book examines how psychological science is, and can be, used to prevent and ameliorate pressing human problems to promote positive social change." - }, - { - "Title":"Psychology: Concepts and Applications", - "Description":"Nevid developed the effective teaching devices in this text based on a comprehensive system derived from research on learning and memory as well as his own research on textbook pedagogy." - }, - { - "Title":"Foundations of Sport and Exercise Psychology, 6E: ", - "Description":"This text offers both students and new practitioners a comprehensive view of sport and exercise psychology, drawing connections between research and practice and capturing the excitement of the world of sport and exercise." - }, - { - "Title":"Biographical Dictionary of Psychology", - "Description":"This Dictionary provides biographical and bibliographical information on over 500 psychologists from all over the world from 1850 to the present day. All branches of psychology and its related disciplines are featured." - }, - { - "Title":"Psychology: A Self-Teaching Guide", - "Description":"Frank Bruno explains all the major psychological theories and terms in this book, covering perception, motivation, thinking, personality, sensation, intelligence, research methods, and much more." - }, - { - "Title":"A Dictionary of Psychology", - "Description":"Entries are extensively cross-referenced for ease of use, and cover word origins and derivations as well as definitions. Over 80 illustrations complement the text." - }, - { - "Title":"An Intellectual History of Psychology", - "Description":"Invaluable as a text for students and as a stimulating and insightful overview for scholars and practicing psychologists, this volume can be read either as a history of psychology in both its philosophical and aspiring scientific periods or" - }] \ No newline at end of file diff --git a/scripts/scrape-google-books.pl b/scripts/scrape-google-books.pl deleted file mode 100644 index d7a96b76..00000000 --- a/scripts/scrape-google-books.pl +++ /dev/null @@ -1,91 +0,0 @@ -use strict; -use warnings; -use HTML::Entities; - -my $url = 'https://www.google.com/search?tbm=bks&'; -my $userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36'; - -print "\n\nPlease enter the query you would like to scrape.\n"; -my $query = ; -$query =~ s/^\s+|\s+$//g; - -print "\nHow many pages?\n"; -my $maxPages = ; -$maxPages =~ s/^\s+|\s+$//g; - -print "\nOutput folder?\n"; -my $folder = ; -$folder =~ s/^\s+|(\\|\/)?\s+$//g; -my $file = "$folder/$query-$maxPages.json"; - -my %hash; - -print "\nFetching...\n"; -for(my $i = 0; $i < $maxPages; $i++) -{ - my $fullUrl = $url . "q=$query"; - if($i != 0) - { - $fullUrl .= "&start=$i" .0; - } - print "Getting this: $fullUrl\n"; - my $html = `curl -A "$userAgent" -k -L "$fullUrl"`; - - #print "HTML: $html"; - #; - #Disclaimer: kids, don't parse HTML like this at home - #it's bad for your health/sanity ;) - my @sections = split /div class="rc"/, $html; - shift @sections; - for my $section (@sections) - { - my $title; - my $desc; - if($section =~ /\s*([^<]+)/) - { - $title = clean($1); - } - if($section =~ /([^<]+)/) - { - $desc = clean($1); - } - - print "\nTITLE:$title:\n\nDESC:$desc:\n"; - $hash{$title} = $desc; - } - - #Google is really quick to shut down botting. - sleep 3; -} - -open FILE, ">:utf8", $file or die "Could not open file to write $!"; -print FILE "[\n"; -my $first = 1; -for my $title (keys %hash) -{ - if($first) - { - $first = 0; - } - else - { - print FILE ",\n"; - } - next unless (defined $title and defined $hash{$title}); - next unless ($title ne '' and $hash{$title} ne ''); - print FILE "\t{\n"; - print FILE "\t\t\"Title\":\"$title\",\n"; - print FILE "\t\t\"Description\":\"$hash{$title}\"\n"; - print FILE "\t}"; -} -print FILE "\n]"; -close FILE; - -sub clean -{ - my $str = $_[0]; - $str = decode_entities($str); - $str =~ s/\s+\.\.\.$//; - $str =~ s/"/'/g; - return $str; -} \ No newline at end of file diff --git a/src/NadekoBot/Attributes/Aliases.cs b/src/NadekoBot/Attributes/Aliases.cs new file mode 100644 index 00000000..af3e98fb --- /dev/null +++ b/src/NadekoBot/Attributes/Aliases.cs @@ -0,0 +1,14 @@ +using Discord.Commands; +using NadekoBot.Services; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace NadekoBot.Attributes +{ + public class Aliases : AliasAttribute + { + public Aliases([CallerMemberName] string memberName = "") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ').Skip(1).ToArray()) + { + } + } +} diff --git a/src/NadekoBot/Attributes/Description.cs b/src/NadekoBot/Attributes/Description.cs new file mode 100644 index 00000000..a6f32c74 --- /dev/null +++ b/src/NadekoBot/Attributes/Description.cs @@ -0,0 +1,14 @@ +using Discord.Commands; +using NadekoBot.Services; +using System.Runtime.CompilerServices; + +namespace NadekoBot.Attributes +{ + public class Description : SummaryAttribute + { + public Description([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_desc")) + { + + } + } +} diff --git a/src/NadekoBot/Attributes/NadekoCommand.cs b/src/NadekoBot/Attributes/NadekoCommand.cs new file mode 100644 index 00000000..3c8010a9 --- /dev/null +++ b/src/NadekoBot/Attributes/NadekoCommand.cs @@ -0,0 +1,14 @@ +using Discord.Commands; +using NadekoBot.Services; +using System.Runtime.CompilerServices; + +namespace NadekoBot.Attributes +{ + public class NadekoCommand : CommandAttribute + { + public NadekoCommand([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant() + "_cmd").Split(' ')[0]) + { + + } + } +} diff --git a/src/NadekoBot/Attributes/NadekoModule.cs b/src/NadekoBot/Attributes/NadekoModule.cs new file mode 100644 index 00000000..a4aba6c6 --- /dev/null +++ b/src/NadekoBot/Attributes/NadekoModule.cs @@ -0,0 +1,48 @@ +using Discord.Commands; +using NadekoBot.Services; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NadekoBot.Attributes +{ + [System.AttributeUsage(AttributeTargets.Class)] + sealed class NadekoModuleAttribute : ModuleAttribute + { + //modulename / prefix + private static Dictionary modulePrefixes = null; + public static Dictionary ModulePrefixes { + get { + if (modulePrefixes != null) + return modulePrefixes; + + using (var uow = DbHandler.UnitOfWork()) + { + return (modulePrefixes = uow.BotConfig + .GetOrCreate() + .ModulePrefixes + .ToDictionary(p => p.ModuleName, p => p.Prefix)); + } + } + } + + public NadekoModuleAttribute(string moduleName, string defaultPrefix) : base(GetModulePrefix(moduleName, defaultPrefix)) + { + AppendSpace = false; + } + + private static string GetModulePrefix(string moduleName, string defaultPrefix) + { + string prefix = null; + if (!ModulePrefixes.TryGetValue(moduleName, out prefix)) + { + NadekoBot.ModulePrefixes.TryAdd(moduleName, defaultPrefix); + NLog.LogManager.GetCurrentClassLogger().Warn("Prefix not found for {0}. Will use default one: {1}", moduleName, defaultPrefix); + } + + + return prefix ?? defaultPrefix; + } + } +} + diff --git a/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs b/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs new file mode 100644 index 00000000..f04b6ef2 --- /dev/null +++ b/src/NadekoBot/Attributes/OwnerOnlyAttribute.cs @@ -0,0 +1,12 @@ +using System.Threading.Tasks; +using Discord.Commands; +using Discord; + +namespace NadekoBot.Attributes +{ + public class OwnerOnlyAttribute : PreconditionAttribute + { + public override Task CheckPermissions(IUserMessage context, Command executingCommand, object moduleInstance) => + Task.FromResult((NadekoBot.Credentials.IsOwner(context.Author) ? PreconditionResult.FromSuccess() : PreconditionResult.FromError("Not owner"))); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Attributes/Usage.cs b/src/NadekoBot/Attributes/Usage.cs new file mode 100644 index 00000000..b3e18519 --- /dev/null +++ b/src/NadekoBot/Attributes/Usage.cs @@ -0,0 +1,14 @@ +using Discord.Commands; +using NadekoBot.Services; +using System.Runtime.CompilerServices; + +namespace NadekoBot.Attributes +{ + public class Usage : RemarksAttribute + { + public Usage([CallerMemberName] string memberName="") : base(Localization.LoadCommandString(memberName.ToLowerInvariant()+"_usage")) + { + + } + } +} diff --git a/src/NadekoBot/DataStructures/ConcurrentHashSet.cs b/src/NadekoBot/DataStructures/ConcurrentHashSet.cs new file mode 100644 index 00000000..2a2ae1bf --- /dev/null +++ b/src/NadekoBot/DataStructures/ConcurrentHashSet.cs @@ -0,0 +1,772 @@ +// License MIT +// Source: https://github.com/i3arnon/ConcurrentHashSet + +using ConcurrentCollections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace System.Collections.Concurrent +{ + ///

+ /// Represents a thread-safe hash-based unique collection. + /// + /// The type of the items in the collection. + /// + /// All public members of are thread-safe and may be used + /// concurrently from multiple threads. + /// + [DebuggerDisplay("Count = {Count}")] + public sealed class ConcurrentHashSet : IReadOnlyCollection, ICollection + { + private const int DefaultCapacity = 31; + private const int MaxLockNumber = 1024; + + private readonly IEqualityComparer _comparer; + private readonly bool _growLockArray; + + private int _budget; + private volatile Tables _tables; + + private static int DefaultConcurrencyLevel => PlatformHelper.ProcessorCount; + + /// + /// Gets the number of items contained in the . + /// + /// The number of items contained in the . + /// Count has snapshot semantics and represents the number of items in the + /// at the moment when Count was accessed. + public int Count { + get { + var count = 0; + var acquiredLocks = 0; + try + { + AcquireAllLocks(ref acquiredLocks); + + for (var i = 0; i < _tables.CountPerLock.Length; i++) + { + count += _tables.CountPerLock[i]; + } + } + finally + { + ReleaseLocks(0, acquiredLocks); + } + + return count; + } + } + + /// + /// Gets a value that indicates whether the is empty. + /// + /// true if the is empty; otherwise, + /// false. + public bool IsEmpty { + get { + var acquiredLocks = 0; + try + { + AcquireAllLocks(ref acquiredLocks); + + for (var i = 0; i < _tables.CountPerLock.Length; i++) + { + if (_tables.CountPerLock[i] != 0) + { + return false; + } + } + } + finally + { + ReleaseLocks(0, acquiredLocks); + } + + return true; + } + } + + /// + /// Initializes a new instance of the + /// class that is empty, has the default concurrency level, has the default initial capacity, and + /// uses the default comparer for the item type. + /// + public ConcurrentHashSet() + : this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer.Default) + { + } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level and capacity, and uses the default + /// comparer for the item type. + /// + /// The estimated number of threads that will update the + /// concurrently. + /// The initial number of elements that the + /// can contain. + /// is + /// less than 1. + /// is less than + /// 0. + public ConcurrentHashSet(int concurrencyLevel, int capacity) + : this(concurrencyLevel, capacity, false, EqualityComparer.Default) + { + } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , has the default concurrency + /// level, has the default initial capacity, and uses the default comparer for the item type. + /// + /// The whose elements are copied to + /// the new + /// . + /// is a null reference. + public ConcurrentHashSet(IEnumerable collection) + : this(collection, EqualityComparer.Default) + { + } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level and capacity, and uses the specified + /// . + /// + /// The + /// implementation to use when comparing items. + /// is a null reference. + public ConcurrentHashSet(IEqualityComparer comparer) + : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) + { + } + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , has the default concurrency level, has the default + /// initial capacity, and uses the specified + /// . + /// + /// The whose elements are copied to + /// the new + /// . + /// The + /// implementation to use when comparing items. + /// is a null reference + /// (Nothing in Visual Basic). -or- + /// is a null reference (Nothing in Visual Basic). + /// + public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) + : this(comparer) + { + if (collection == null) throw new ArgumentNullException(nameof(collection)); + + InitializeFromCollection(collection); + } + + + /// + /// Initializes a new instance of the + /// class that contains elements copied from the specified , + /// has the specified concurrency level, has the specified initial capacity, and uses the specified + /// . + /// + /// The estimated number of threads that will update the + /// concurrently. + /// The whose elements are copied to the new + /// . + /// The implementation to use + /// when comparing items. + /// + /// is a null reference. + /// -or- + /// is a null reference. + /// + /// + /// is less than 1. + /// + public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) + : this(concurrencyLevel, DefaultCapacity, false, comparer) + { + if (collection == null) throw new ArgumentNullException(nameof(collection)); + if (comparer == null) throw new ArgumentNullException(nameof(comparer)); + + InitializeFromCollection(collection); + } + + /// + /// Initializes a new instance of the + /// class that is empty, has the specified concurrency level, has the specified initial capacity, and + /// uses the specified . + /// + /// The estimated number of threads that will update the + /// concurrently. + /// The initial number of elements that the + /// can contain. + /// The + /// implementation to use when comparing items. + /// + /// is less than 1. -or- + /// is less than 0. + /// + /// is a null reference. + public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer comparer) + : this(concurrencyLevel, capacity, false, comparer) + { + } + + private ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer comparer) + { + if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel)); + if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); + if (comparer == null) throw new ArgumentNullException(nameof(comparer)); + + // The capacity should be at least as large as the concurrency level. Otherwise, we would have locks that don't guard + // any buckets. + if (capacity < concurrencyLevel) + { + capacity = concurrencyLevel; + } + + var locks = new object[concurrencyLevel]; + for (var i = 0; i < locks.Length; i++) + { + locks[i] = new object(); + } + + var countPerLock = new int[locks.Length]; + var buckets = new Node[capacity]; + _tables = new Tables(buckets, locks, countPerLock); + + _growLockArray = growLockArray; + _budget = buckets.Length / locks.Length; + _comparer = comparer; + } + + /// + /// Adds the specified item to the . + /// + /// The item to add. + /// true if the items was added to the + /// successfully; false if it already exists. + /// The + /// contains too many items. + public bool Add(T item) => + AddInternal(item, _comparer.GetHashCode(item), true); + + /// + /// Removes all items from the . + /// + public void Clear() + { + var locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + + var newTables = new Tables(new Node[DefaultCapacity], _tables.Locks, new int[_tables.CountPerLock.Length]); + _tables = newTables; + _budget = Math.Max(1, newTables.Buckets.Length / newTables.Locks.Length); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + /// + /// Determines whether the contains the specified + /// item. + /// + /// The item to locate in the . + /// true if the contains the item; otherwise, false. + public bool Contains(T item) + { + var hashcode = _comparer.GetHashCode(item); + + // We must capture the _buckets field in a local variable. It is set to a new table on each table resize. + var tables = _tables; + + var bucketNo = GetBucket(hashcode, tables.Buckets.Length); + + // We can get away w/out a lock here. + // The Volatile.Read ensures that the load of the fields of 'n' doesn't move before the load from buckets[i]. + var current = Volatile.Read(ref tables.Buckets[bucketNo]); + + while (current != null) + { + if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item)) + { + return true; + } + current = current.Next; + } + + return false; + } + + /// + /// Attempts to remove the item from the . + /// + /// The item to remove. + /// true if an item was removed successfully; otherwise, false. + public bool TryRemove(T item) + { + var hashcode = _comparer.GetHashCode(item); + while (true) + { + var tables = _tables; + + int bucketNo, lockNo; + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.Buckets.Length, tables.Locks.Length); + + lock (tables.Locks[lockNo]) + { + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurrence. + if (tables != _tables) + { + continue; + } + + Node previous = null; + for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next) + { + Debug.Assert((previous == null && current == tables.Buckets[bucketNo]) || previous.Next == current); + + if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item)) + { + if (previous == null) + { + Volatile.Write(ref tables.Buckets[bucketNo], current.Next); + } + else + { + previous.Next = current.Next; + } + + tables.CountPerLock[lockNo]--; + return true; + } + previous = current; + } + } + + return false; + } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// Returns an enumerator that iterates through the . + /// An enumerator for the . + /// + /// The enumerator returned from the collection is safe to use concurrently with + /// reads and writes to the collection, however it does not represent a moment-in-time snapshot + /// of the collection. The contents exposed through the enumerator may contain modifications + /// made to the collection after was called. + /// + public IEnumerator GetEnumerator() + { + var buckets = _tables.Buckets; + + for (var i = 0; i < buckets.Length; i++) + { + // The Volatile.Read ensures that the load of the fields of 'current' doesn't move before the load from buckets[i]. + var current = Volatile.Read(ref buckets[i]); + + while (current != null) + { + yield return current.Item; + current = current.Next; + } + } + } + + void ICollection.Add(T item) => Add(item); + + bool ICollection.IsReadOnly => false; + + void ICollection.CopyTo(T[] array, int arrayIndex) + { + if (array == null) throw new ArgumentNullException(nameof(array)); + if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + + var locksAcquired = 0; + try + { + AcquireAllLocks(ref locksAcquired); + + var count = 0; + + for (var i = 0; i < _tables.Locks.Length && count >= 0; i++) + { + count += _tables.CountPerLock[i]; + } + + if (array.Length - count < arrayIndex || count < 0) //"count" itself or "count + arrayIndex" can overflow + { + throw new ArgumentException("The index is equal to or greater than the length of the array, or the number of elements in the set is greater than the available space from index to the end of the destination array."); + } + + CopyToItems(array, arrayIndex); + } + finally + { + ReleaseLocks(0, locksAcquired); + } + } + + bool ICollection.Remove(T item) => TryRemove(item); + + private void InitializeFromCollection(IEnumerable collection) + { + foreach (var item in collection) + { + AddInternal(item, _comparer.GetHashCode(item), false); + } + + if (_budget == 0) + { + _budget = _tables.Buckets.Length / _tables.Locks.Length; + } + } + + private bool AddInternal(T item, int hashcode, bool acquireLock) + { + while (true) + { + int bucketNo, lockNo; + + var tables = _tables; + GetBucketAndLockNo(hashcode, out bucketNo, out lockNo, tables.Buckets.Length, tables.Locks.Length); + + var resizeDesired = false; + var lockTaken = false; + try + { + if (acquireLock) + Monitor.Enter(tables.Locks[lockNo], ref lockTaken); + + // If the table just got resized, we may not be holding the right lock, and must retry. + // This should be a rare occurrence. + if (tables != _tables) + { + continue; + } + + // Try to find this item in the bucket + Node previous = null; + for (var current = tables.Buckets[bucketNo]; current != null; current = current.Next) + { + Debug.Assert((previous == null && current == tables.Buckets[bucketNo]) || previous.Next == current); + if (hashcode == current.Hashcode && _comparer.Equals(current.Item, item)) + { + return false; + } + previous = current; + } + + // The item was not found in the bucket. Insert the new item. + Volatile.Write(ref tables.Buckets[bucketNo], new Node(item, hashcode, tables.Buckets[bucketNo])); + checked + { + tables.CountPerLock[lockNo]++; + } + + // + // If the number of elements guarded by this lock has exceeded the budget, resize the bucket table. + // It is also possible that GrowTable will increase the budget but won't resize the bucket table. + // That happens if the bucket table is found to be poorly utilized due to a bad hash function. + // + if (tables.CountPerLock[lockNo] > _budget) + { + resizeDesired = true; + } + } + finally + { + if (lockTaken) + Monitor.Exit(tables.Locks[lockNo]); + } + + // + // The fact that we got here means that we just performed an insertion. If necessary, we will grow the table. + // + // Concurrency notes: + // - Notice that we are not holding any locks at when calling GrowTable. This is necessary to prevent deadlocks. + // - As a result, it is possible that GrowTable will be called unnecessarily. But, GrowTable will obtain lock 0 + // and then verify that the table we passed to it as the argument is still the current table. + // + if (resizeDesired) + { + GrowTable(tables); + } + + return true; + } + } + + private static int GetBucket(int hashcode, int bucketCount) + { + var bucketNo = (hashcode & 0x7fffffff) % bucketCount; + Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount); + return bucketNo; + } + + private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) + { + bucketNo = (hashcode & 0x7fffffff) % bucketCount; + lockNo = bucketNo % lockCount; + + Debug.Assert(bucketNo >= 0 && bucketNo < bucketCount); + Debug.Assert(lockNo >= 0 && lockNo < lockCount); + } + + private void GrowTable(Tables tables) + { + const int maxArrayLength = 0X7FEFFFFF; + var locksAcquired = 0; + try + { + // The thread that first obtains _locks[0] will be the one doing the resize operation + AcquireLocks(0, 1, ref locksAcquired); + + // Make sure nobody resized the table while we were waiting for lock 0: + if (tables != _tables) + { + // We assume that since the table reference is different, it was already resized (or the budget + // was adjusted). If we ever decide to do table shrinking, or replace the table for other reasons, + // we will have to revisit this logic. + return; + } + + // Compute the (approx.) total size. Use an Int64 accumulation variable to avoid an overflow. + long approxCount = 0; + for (var i = 0; i < tables.CountPerLock.Length; i++) + { + approxCount += tables.CountPerLock[i]; + } + + // + // If the bucket array is too empty, double the budget instead of resizing the table + // + if (approxCount < tables.Buckets.Length / 4) + { + _budget = 2 * _budget; + if (_budget < 0) + { + _budget = int.MaxValue; + } + return; + } + + // Compute the new table size. We find the smallest integer larger than twice the previous table size, and not divisible by + // 2,3,5 or 7. We can consider a different table-sizing policy in the future. + var newLength = 0; + var maximizeTableSize = false; + try + { + checked + { + // Double the size of the buckets table and add one, so that we have an odd integer. + newLength = tables.Buckets.Length * 2 + 1; + + // Now, we only need to check odd integers, and find the first that is not divisible + // by 3, 5 or 7. + while (newLength % 3 == 0 || newLength % 5 == 0 || newLength % 7 == 0) + { + newLength += 2; + } + + Debug.Assert(newLength % 2 != 0); + + if (newLength > maxArrayLength) + { + maximizeTableSize = true; + } + } + } + catch (OverflowException) + { + maximizeTableSize = true; + } + + if (maximizeTableSize) + { + newLength = maxArrayLength; + + // We want to make sure that GrowTable will not be called again, since table is at the maximum size. + // To achieve that, we set the budget to int.MaxValue. + // + // (There is one special case that would allow GrowTable() to be called in the future: + // calling Clear() on the ConcurrentHashSet will shrink the table and lower the budget.) + _budget = int.MaxValue; + } + + // Now acquire all other locks for the table + AcquireLocks(1, tables.Locks.Length, ref locksAcquired); + + var newLocks = tables.Locks; + + // Add more locks + if (_growLockArray && tables.Locks.Length < MaxLockNumber) + { + newLocks = new object[tables.Locks.Length * 2]; + Array.Copy(tables.Locks, 0, newLocks, 0, tables.Locks.Length); + for (var i = tables.Locks.Length; i < newLocks.Length; i++) + { + newLocks[i] = new object(); + } + } + + var newBuckets = new Node[newLength]; + var newCountPerLock = new int[newLocks.Length]; + + // Copy all data into a new table, creating new nodes for all elements + for (var i = 0; i < tables.Buckets.Length; i++) + { + var current = tables.Buckets[i]; + while (current != null) + { + var next = current.Next; + int newBucketNo, newLockNo; + GetBucketAndLockNo(current.Hashcode, out newBucketNo, out newLockNo, newBuckets.Length, newLocks.Length); + + newBuckets[newBucketNo] = new Node(current.Item, current.Hashcode, newBuckets[newBucketNo]); + + checked + { + newCountPerLock[newLockNo]++; + } + + current = next; + } + } + + // Adjust the budget + _budget = Math.Max(1, newBuckets.Length / newLocks.Length); + + // Replace tables with the new versions + _tables = new Tables(newBuckets, newLocks, newCountPerLock); + } + finally + { + // Release all locks that we took earlier + ReleaseLocks(0, locksAcquired); + } + } + + public int RemoveWhere(Func predicate) + { + var elems = this.Where(predicate); + var removed = 0; + foreach (var elem in elems) + { + if (this.TryRemove(elem)) + removed++; + } + return removed; + } + + private void AcquireAllLocks(ref int locksAcquired) + { + // First, acquire lock 0 + AcquireLocks(0, 1, ref locksAcquired); + + // Now that we have lock 0, the _locks array will not change (i.e., grow), + // and so we can safely read _locks.Length. + AcquireLocks(1, _tables.Locks.Length, ref locksAcquired); + Debug.Assert(locksAcquired == _tables.Locks.Length); + } + + private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) + { + Debug.Assert(fromInclusive <= toExclusive); + var locks = _tables.Locks; + + for (var i = fromInclusive; i < toExclusive; i++) + { + var lockTaken = false; + try + { + Monitor.Enter(locks[i], ref lockTaken); + } + finally + { + if (lockTaken) + { + locksAcquired++; + } + } + } + } + + private void ReleaseLocks(int fromInclusive, int toExclusive) + { + Debug.Assert(fromInclusive <= toExclusive); + + for (var i = fromInclusive; i < toExclusive; i++) + { + Monitor.Exit(_tables.Locks[i]); + } + } + + private void CopyToItems(T[] array, int index) + { + var buckets = _tables.Buckets; + for (var i = 0; i < buckets.Length; i++) + { + for (var current = buckets[i]; current != null; current = current.Next) + { + array[index] = current.Item; + index++; //this should never flow, CopyToItems is only called when there's no overflow risk + } + } + } + + private sealed class Tables + { + public readonly Node[] Buckets; + public readonly object[] Locks; + + public volatile int[] CountPerLock; + + public Tables(Node[] buckets, object[] locks, int[] countPerLock) + { + Buckets = buckets; + Locks = locks; + CountPerLock = countPerLock; + } + } + + private sealed class Node + { + public readonly T Item; + public readonly int Hashcode; + + public volatile Node Next; + + public Node(T item, int hashcode, Node next) + { + Item = item; + Hashcode = hashcode; + Next = next; + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/DataStructures/PlatformHelper.cs b/src/NadekoBot/DataStructures/PlatformHelper.cs new file mode 100644 index 00000000..8c523ec9 --- /dev/null +++ b/src/NadekoBot/DataStructures/PlatformHelper.cs @@ -0,0 +1,25 @@ +using System; + +namespace ConcurrentCollections +{ + public static class PlatformHelper + { + private const int ProcessorCountRefreshIntervalMs = 30000; + + private static volatile int _processorCount; + private static volatile int _lastProcessorCountRefreshTicks; + + public static int ProcessorCount { + get { + var now = Environment.TickCount; + if (_processorCount == 0 || (now - _lastProcessorCountRefreshTicks) >= ProcessorCountRefreshIntervalMs) + { + _processorCount = Environment.ProcessorCount; + _lastProcessorCountRefreshTicks = now; + } + + return _processorCount; + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Migrations/20161011200458_first.Designer.cs b/src/NadekoBot/Migrations/20161011200458_first.Designer.cs new file mode 100644 index 00000000..3f80251a --- /dev/null +++ b/src/NadekoBot/Migrations/20161011200458_first.Designer.cs @@ -0,0 +1,759 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20161011200458_first")] + partial class first + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.0-rtm-21431"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BufferSize"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("IsLogging"); + + b.Property("LogUserPresence"); + + b.Property("LogVoicePresence"); + + b.Property("MessageDeleted"); + + b.Property("MessageUpdated"); + + b.Property("UserBanned"); + + b.Property("UserJoined"); + + b.Property("UserLeft"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUpdated"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.ToTable("Repeaters"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20161011200458_first.cs b/src/NadekoBot/Migrations/20161011200458_first.cs new file mode 100644 index 00000000..25b3a717 --- /dev/null +++ b/src/NadekoBot/Migrations/20161011200458_first.cs @@ -0,0 +1,804 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class first : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BotConfig", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + BufferSize = table.Column(nullable: false), + CurrencyGenerationChance = table.Column(nullable: false), + CurrencyGenerationCooldown = table.Column(nullable: false), + CurrencyName = table.Column(nullable: true), + CurrencyPluralName = table.Column(nullable: true), + CurrencySign = table.Column(nullable: true), + DMHelpString = table.Column(nullable: true), + ForwardMessages = table.Column(nullable: false), + ForwardToAllOwners = table.Column(nullable: false), + HelpString = table.Column(nullable: true), + MigrationVersion = table.Column(nullable: false), + RemindMessageFormat = table.Column(nullable: true), + RotatingStatuses = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BotConfig", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ClashOfClans", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelId = table.Column(nullable: false), + EnemyClan = table.Column(nullable: true), + GuildId = table.Column(nullable: false), + Size = table.Column(nullable: false), + StartedAt = table.Column(nullable: false), + WarState = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClashOfClans", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ConversionUnits", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + InternalTrigger = table.Column(nullable: true), + Modifier = table.Column(nullable: false), + UnitType = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ConversionUnits", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Currency", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + Amount = table.Column(nullable: false), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Currency", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CustomReactions", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + GuildId = table.Column(nullable: true), + IsRegex = table.Column(nullable: false), + OwnerOnly = table.Column(nullable: false), + Response = table.Column(nullable: true), + Trigger = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CustomReactions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Donators", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + Amount = table.Column(nullable: false), + Name = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Donators", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "LogSettings", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelCreated = table.Column(nullable: false), + ChannelDestroyed = table.Column(nullable: false), + ChannelId = table.Column(nullable: false), + ChannelUpdated = table.Column(nullable: false), + IsLogging = table.Column(nullable: false), + LogUserPresence = table.Column(nullable: false), + LogVoicePresence = table.Column(nullable: false), + MessageDeleted = table.Column(nullable: false), + MessageUpdated = table.Column(nullable: false), + UserBanned = table.Column(nullable: false), + UserJoined = table.Column(nullable: false), + UserLeft = table.Column(nullable: false), + UserPresenceChannelId = table.Column(nullable: false), + UserUnbanned = table.Column(nullable: false), + UserUpdated = table.Column(nullable: false), + VoicePresenceChannelId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_LogSettings", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "MusicPlaylists", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + Author = table.Column(nullable: true), + AuthorId = table.Column(nullable: false), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_MusicPlaylists", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Permission", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + NextId = table.Column(nullable: true), + PrimaryTarget = table.Column(nullable: false), + PrimaryTargetId = table.Column(nullable: false), + SecondaryTarget = table.Column(nullable: false), + SecondaryTargetName = table.Column(nullable: true), + State = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Permission", x => x.Id); + table.ForeignKey( + name: "FK_Permission_Permission_NextId", + column: x => x.NextId, + principalTable: "Permission", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "Quotes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + AuthorId = table.Column(nullable: false), + AuthorName = table.Column(nullable: false), + GuildId = table.Column(nullable: false), + Keyword = table.Column(nullable: false), + Text = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Quotes", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Reminders", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelId = table.Column(nullable: false), + IsPrivate = table.Column(nullable: false), + Message = table.Column(nullable: true), + ServerId = table.Column(nullable: false), + UserId = table.Column(nullable: false), + When = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Reminders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Repeaters", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelId = table.Column(nullable: false), + GuildId = table.Column(nullable: false), + Interval = table.Column(nullable: false), + Message = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Repeaters", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SelfAssignableRoles", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + GuildId = table.Column(nullable: false), + RoleId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SelfAssignableRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BlacklistItem", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + BotConfigId = table.Column(nullable: true), + ItemId = table.Column(nullable: false), + Type = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlacklistItem", x => x.Id); + table.ForeignKey( + name: "FK_BlacklistItem_BotConfig_BotConfigId", + column: x => x.BotConfigId, + principalTable: "BotConfig", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "EightBallResponses", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + BotConfigId = table.Column(nullable: true), + Text = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_EightBallResponses", x => x.Id); + table.ForeignKey( + name: "FK_EightBallResponses_BotConfig_BotConfigId", + column: x => x.BotConfigId, + principalTable: "BotConfig", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ModulePrefixes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + BotConfigId = table.Column(nullable: true), + ModuleName = table.Column(nullable: true), + Prefix = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ModulePrefixes", x => x.Id); + table.ForeignKey( + name: "FK_ModulePrefixes_BotConfig_BotConfigId", + column: x => x.BotConfigId, + principalTable: "BotConfig", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "PlayingStatus", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + BotConfigId = table.Column(nullable: true), + Status = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlayingStatus", x => x.Id); + table.ForeignKey( + name: "FK_PlayingStatus_BotConfig_BotConfigId", + column: x => x.BotConfigId, + principalTable: "BotConfig", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "RaceAnimals", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + BotConfigId = table.Column(nullable: true), + Icon = table.Column(nullable: true), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RaceAnimals", x => x.Id); + table.ForeignKey( + name: "FK_RaceAnimals_BotConfig_BotConfigId", + column: x => x.BotConfigId, + principalTable: "BotConfig", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "ClashCallers", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + BaseDestroyed = table.Column(nullable: false), + CallUser = table.Column(nullable: true), + ClashWarId = table.Column(nullable: false), + Stars = table.Column(nullable: false), + TimeAdded = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClashCallers", x => x.Id); + table.ForeignKey( + name: "FK_ClashCallers_ClashOfClans_ClashWarId", + column: x => x.ClashWarId, + principalTable: "ClashOfClans", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IgnoredLogChannels", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelId = table.Column(nullable: false), + LogSettingId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IgnoredLogChannels", x => x.Id); + table.ForeignKey( + name: "FK_IgnoredLogChannels_LogSettings_LogSettingId", + column: x => x.LogSettingId, + principalTable: "LogSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "IgnoredVoicePresenceCHannels", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelId = table.Column(nullable: false), + LogSettingId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_IgnoredVoicePresenceCHannels", x => x.Id); + table.ForeignKey( + name: "FK_IgnoredVoicePresenceCHannels_LogSettings_LogSettingId", + column: x => x.LogSettingId, + principalTable: "LogSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "PlaylistSong", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + MusicPlaylistId = table.Column(nullable: true), + Provider = table.Column(nullable: true), + ProviderType = table.Column(nullable: false), + Query = table.Column(nullable: true), + Title = table.Column(nullable: true), + Uri = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_PlaylistSong", x => x.Id); + table.ForeignKey( + name: "FK_PlaylistSong_MusicPlaylists_MusicPlaylistId", + column: x => x.MusicPlaylistId, + principalTable: "MusicPlaylists", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "GuildConfigs", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + AutoAssignRoleId = table.Column(nullable: false), + AutoDeleteByeMessages = table.Column(nullable: false), + AutoDeleteGreetMessages = table.Column(nullable: false), + AutoDeleteGreetMessagesTimer = table.Column(nullable: false), + AutoDeleteSelfAssignedRoleMessages = table.Column(nullable: false), + ByeMessageChannelId = table.Column(nullable: false), + ChannelByeMessageText = table.Column(nullable: true), + ChannelGreetMessageText = table.Column(nullable: true), + DefaultMusicVolume = table.Column(nullable: false), + DeleteMessageOnCommand = table.Column(nullable: false), + DmGreetMessageText = table.Column(nullable: true), + ExclusiveSelfAssignedRoles = table.Column(nullable: false), + FilterInvites = table.Column(nullable: false), + FilterWords = table.Column(nullable: false), + GreetMessageChannelId = table.Column(nullable: false), + GuildId = table.Column(nullable: false), + LogSettingId = table.Column(nullable: true), + PermissionRole = table.Column(nullable: true), + RootPermissionId = table.Column(nullable: true), + SendChannelByeMessage = table.Column(nullable: false), + SendChannelGreetMessage = table.Column(nullable: false), + SendDmGreetMessage = table.Column(nullable: false), + VerbosePermissions = table.Column(nullable: false), + VoicePlusTextEnabled = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GuildConfigs", x => x.Id); + table.ForeignKey( + name: "FK_GuildConfigs_LogSettings_LogSettingId", + column: x => x.LogSettingId, + principalTable: "LogSettings", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_GuildConfigs_Permission_RootPermissionId", + column: x => x.RootPermissionId, + principalTable: "Permission", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "CommandCooldown", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + CommandName = table.Column(nullable: true), + GuildConfigId = table.Column(nullable: true), + Seconds = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CommandCooldown", x => x.Id); + table.ForeignKey( + name: "FK_CommandCooldown_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "FilterChannelId", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelId = table.Column(nullable: false), + GuildConfigId = table.Column(nullable: true), + GuildConfigId1 = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_FilterChannelId", x => x.Id); + table.ForeignKey( + name: "FK_FilterChannelId_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + table.ForeignKey( + name: "FK_FilterChannelId_GuildConfigs_GuildConfigId1", + column: x => x.GuildConfigId1, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "FilteredWord", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + GuildConfigId = table.Column(nullable: true), + Word = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_FilteredWord", x => x.Id); + table.ForeignKey( + name: "FK_FilteredWord_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "FollowedStream", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelId = table.Column(nullable: false), + GuildConfigId = table.Column(nullable: true), + GuildId = table.Column(nullable: false), + Type = table.Column(nullable: false), + Username = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_FollowedStream", x => x.Id); + table.ForeignKey( + name: "FK_FollowedStream_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "GCChannelId", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + ChannelId = table.Column(nullable: false), + GuildConfigId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_GCChannelId", x => x.Id); + table.ForeignKey( + name: "FK_GCChannelId_GuildConfigs_GuildConfigId", + column: x => x.GuildConfigId, + principalTable: "GuildConfigs", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_BlacklistItem_BotConfigId", + table: "BlacklistItem", + column: "BotConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_ClashCallers_ClashWarId", + table: "ClashCallers", + column: "ClashWarId"); + + migrationBuilder.CreateIndex( + name: "IX_CommandCooldown_GuildConfigId", + table: "CommandCooldown", + column: "GuildConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_Currency_UserId", + table: "Currency", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_Donators_UserId", + table: "Donators", + column: "UserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_EightBallResponses_BotConfigId", + table: "EightBallResponses", + column: "BotConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_FilterChannelId_GuildConfigId", + table: "FilterChannelId", + column: "GuildConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_FilterChannelId_GuildConfigId1", + table: "FilterChannelId", + column: "GuildConfigId1"); + + migrationBuilder.CreateIndex( + name: "IX_FilteredWord_GuildConfigId", + table: "FilteredWord", + column: "GuildConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_FollowedStream_GuildConfigId", + table: "FollowedStream", + column: "GuildConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_GCChannelId_GuildConfigId", + table: "GCChannelId", + column: "GuildConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_GuildConfigs_GuildId", + table: "GuildConfigs", + column: "GuildId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_GuildConfigs_LogSettingId", + table: "GuildConfigs", + column: "LogSettingId"); + + migrationBuilder.CreateIndex( + name: "IX_GuildConfigs_RootPermissionId", + table: "GuildConfigs", + column: "RootPermissionId"); + + migrationBuilder.CreateIndex( + name: "IX_IgnoredLogChannels_LogSettingId", + table: "IgnoredLogChannels", + column: "LogSettingId"); + + migrationBuilder.CreateIndex( + name: "IX_IgnoredVoicePresenceCHannels_LogSettingId", + table: "IgnoredVoicePresenceCHannels", + column: "LogSettingId"); + + migrationBuilder.CreateIndex( + name: "IX_ModulePrefixes_BotConfigId", + table: "ModulePrefixes", + column: "BotConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_Permission_NextId", + table: "Permission", + column: "NextId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_PlayingStatus_BotConfigId", + table: "PlayingStatus", + column: "BotConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_PlaylistSong_MusicPlaylistId", + table: "PlaylistSong", + column: "MusicPlaylistId"); + + migrationBuilder.CreateIndex( + name: "IX_RaceAnimals_BotConfigId", + table: "RaceAnimals", + column: "BotConfigId"); + + migrationBuilder.CreateIndex( + name: "IX_Repeaters_ChannelId", + table: "Repeaters", + column: "ChannelId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SelfAssignableRoles_GuildId_RoleId", + table: "SelfAssignableRoles", + columns: new[] { "GuildId", "RoleId" }, + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BlacklistItem"); + + migrationBuilder.DropTable( + name: "ClashCallers"); + + migrationBuilder.DropTable( + name: "CommandCooldown"); + + migrationBuilder.DropTable( + name: "ConversionUnits"); + + migrationBuilder.DropTable( + name: "Currency"); + + migrationBuilder.DropTable( + name: "CustomReactions"); + + migrationBuilder.DropTable( + name: "Donators"); + + migrationBuilder.DropTable( + name: "EightBallResponses"); + + migrationBuilder.DropTable( + name: "FilterChannelId"); + + migrationBuilder.DropTable( + name: "FilteredWord"); + + migrationBuilder.DropTable( + name: "FollowedStream"); + + migrationBuilder.DropTable( + name: "GCChannelId"); + + migrationBuilder.DropTable( + name: "IgnoredLogChannels"); + + migrationBuilder.DropTable( + name: "IgnoredVoicePresenceCHannels"); + + migrationBuilder.DropTable( + name: "ModulePrefixes"); + + migrationBuilder.DropTable( + name: "PlayingStatus"); + + migrationBuilder.DropTable( + name: "PlaylistSong"); + + migrationBuilder.DropTable( + name: "Quotes"); + + migrationBuilder.DropTable( + name: "RaceAnimals"); + + migrationBuilder.DropTable( + name: "Reminders"); + + migrationBuilder.DropTable( + name: "Repeaters"); + + migrationBuilder.DropTable( + name: "SelfAssignableRoles"); + + migrationBuilder.DropTable( + name: "ClashOfClans"); + + migrationBuilder.DropTable( + name: "GuildConfigs"); + + migrationBuilder.DropTable( + name: "MusicPlaylists"); + + migrationBuilder.DropTable( + name: "BotConfig"); + + migrationBuilder.DropTable( + name: "LogSettings"); + + migrationBuilder.DropTable( + name: "Permission"); + } + } +} diff --git a/src/NadekoBot/Migrations/20161015005020_CurrencyTransaction.Designer.cs b/src/NadekoBot/Migrations/20161015005020_CurrencyTransaction.Designer.cs new file mode 100644 index 00000000..52db3bf9 --- /dev/null +++ b/src/NadekoBot/Migrations/20161015005020_CurrencyTransaction.Designer.cs @@ -0,0 +1,775 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20161015005020_CurrencyTransaction")] + partial class CurrencyTransaction + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.0-rtm-21431"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BufferSize"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("IsLogging"); + + b.Property("LogUserPresence"); + + b.Property("LogVoicePresence"); + + b.Property("MessageDeleted"); + + b.Property("MessageUpdated"); + + b.Property("UserBanned"); + + b.Property("UserJoined"); + + b.Property("UserLeft"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUpdated"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.ToTable("Repeaters"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20161015005020_CurrencyTransaction.cs b/src/NadekoBot/Migrations/20161015005020_CurrencyTransaction.cs new file mode 100644 index 00000000..2540fbc5 --- /dev/null +++ b/src/NadekoBot/Migrations/20161015005020_CurrencyTransaction.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class CurrencyTransaction : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CurrencyTransactions", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Autoincrement", true), + Amount = table.Column(nullable: false), + Reason = table.Column(nullable: true), + UserId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_CurrencyTransactions", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CurrencyTransactions"); + } + } +} diff --git a/src/NadekoBot/Migrations/20161015102407_coc.Designer.cs b/src/NadekoBot/Migrations/20161015102407_coc.Designer.cs new file mode 100644 index 00000000..fd104e1a --- /dev/null +++ b/src/NadekoBot/Migrations/20161015102407_coc.Designer.cs @@ -0,0 +1,777 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20161015102407_coc")] + partial class coc + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.0-rtm-21431"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BufferSize"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("IsLogging"); + + b.Property("LogUserPresence"); + + b.Property("LogVoicePresence"); + + b.Property("MessageDeleted"); + + b.Property("MessageUpdated"); + + b.Property("UserBanned"); + + b.Property("UserJoined"); + + b.Property("UserLeft"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUpdated"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.ToTable("Repeaters"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20161015102407_coc.cs b/src/NadekoBot/Migrations/20161015102407_coc.cs new file mode 100644 index 00000000..e6dabaef --- /dev/null +++ b/src/NadekoBot/Migrations/20161015102407_coc.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class coc : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "SequenceNumber", + table: "ClashCallers", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SequenceNumber", + table: "ClashCallers"); + } + } +} diff --git a/src/NadekoBot/Migrations/20161019055137_MuteRoleName.Designer.cs b/src/NadekoBot/Migrations/20161019055137_MuteRoleName.Designer.cs new file mode 100644 index 00000000..f86d532c --- /dev/null +++ b/src/NadekoBot/Migrations/20161019055137_MuteRoleName.Designer.cs @@ -0,0 +1,779 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20161019055137_MuteRoleName")] + partial class MuteRoleName + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.0-rtm-21431"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BufferSize"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("IsLogging"); + + b.Property("LogUserPresence"); + + b.Property("LogVoicePresence"); + + b.Property("MessageDeleted"); + + b.Property("MessageUpdated"); + + b.Property("UserBanned"); + + b.Property("UserJoined"); + + b.Property("UserLeft"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUpdated"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.ToTable("Repeaters"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20161019055137_MuteRoleName.cs b/src/NadekoBot/Migrations/20161019055137_MuteRoleName.cs new file mode 100644 index 00000000..12b26d37 --- /dev/null +++ b/src/NadekoBot/Migrations/20161019055137_MuteRoleName.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class MuteRoleName : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MuteRoleName", + table: "GuildConfigs", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MuteRoleName", + table: "GuildConfigs"); + } + } +} diff --git a/src/NadekoBot/Migrations/20161107213222_Cleverbot.Designer.cs b/src/NadekoBot/Migrations/20161107213222_Cleverbot.Designer.cs new file mode 100644 index 00000000..50ac3309 --- /dev/null +++ b/src/NadekoBot/Migrations/20161107213222_Cleverbot.Designer.cs @@ -0,0 +1,781 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20161107213222_Cleverbot")] + partial class Cleverbot + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.0.0-rtm-21431"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BufferSize"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("IsLogging"); + + b.Property("LogUserPresence"); + + b.Property("LogVoicePresence"); + + b.Property("MessageDeleted"); + + b.Property("MessageUpdated"); + + b.Property("UserBanned"); + + b.Property("UserJoined"); + + b.Property("UserLeft"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUpdated"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.ToTable("Repeaters"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20161107213222_Cleverbot.cs b/src/NadekoBot/Migrations/20161107213222_Cleverbot.cs new file mode 100644 index 00000000..a8bdb0dd --- /dev/null +++ b/src/NadekoBot/Migrations/20161107213222_Cleverbot.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class Cleverbot : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CleverbotEnabled", + table: "GuildConfigs", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CleverbotEnabled", + table: "GuildConfigs"); + } + } +} diff --git a/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.Designer.cs b/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.Designer.cs new file mode 100644 index 00000000..51d8ca7f --- /dev/null +++ b/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.Designer.cs @@ -0,0 +1,785 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using NadekoBot.Modules.Music.Classes; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + [Migration("20161122100602_Greet and bye improved")] + partial class Greetandbyeimproved + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BufferSize"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("IsLogging"); + + b.Property("LogUserPresence"); + + b.Property("LogVoicePresence"); + + b.Property("MessageDeleted"); + + b.Property("MessageUpdated"); + + b.Property("UserBanned"); + + b.Property("UserJoined"); + + b.Property("UserLeft"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUpdated"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.ToTable("Repeaters"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.cs b/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.cs new file mode 100644 index 00000000..8dabc472 --- /dev/null +++ b/src/NadekoBot/Migrations/20161122100602_Greet and bye improved.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace NadekoBot.Migrations +{ + public partial class Greetandbyeimproved : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AutoDeleteByeMessagesTimer", + table: "GuildConfigs", + nullable: false, + defaultValue: 0); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AutoDeleteByeMessagesTimer", + table: "GuildConfigs"); + } + } +} diff --git a/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs new file mode 100644 index 00000000..dd4b8543 --- /dev/null +++ b/src/NadekoBot/Migrations/NadekoSqliteContextModelSnapshot.cs @@ -0,0 +1,784 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using NadekoBot.Modules.Music.Classes; + +namespace NadekoBot.Migrations +{ + [DbContext(typeof(NadekoContext))] + partial class NadekoSqliteContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.0-rtm-22752"); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ItemId"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("BlacklistItem"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BufferSize"); + + b.Property("CurrencyGenerationChance"); + + b.Property("CurrencyGenerationCooldown"); + + b.Property("CurrencyName"); + + b.Property("CurrencyPluralName"); + + b.Property("CurrencySign"); + + b.Property("DMHelpString"); + + b.Property("ForwardMessages"); + + b.Property("ForwardToAllOwners"); + + b.Property("HelpString"); + + b.Property("MigrationVersion"); + + b.Property("RemindMessageFormat"); + + b.Property("RotatingStatuses"); + + b.HasKey("Id"); + + b.ToTable("BotConfig"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BaseDestroyed"); + + b.Property("CallUser"); + + b.Property("ClashWarId"); + + b.Property("SequenceNumber"); + + b.Property("Stars"); + + b.Property("TimeAdded"); + + b.HasKey("Id"); + + b.HasIndex("ClashWarId"); + + b.ToTable("ClashCallers"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashWar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("EnemyClan"); + + b.Property("GuildId"); + + b.Property("Size"); + + b.Property("StartedAt"); + + b.Property("WarState"); + + b.HasKey("Id"); + + b.ToTable("ClashOfClans"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommandName"); + + b.Property("GuildConfigId"); + + b.Property("Seconds"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("CommandCooldown"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ConvertUnit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("InternalTrigger"); + + b.Property("Modifier"); + + b.Property("UnitType"); + + b.HasKey("Id"); + + b.ToTable("ConversionUnits"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Currency", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Currency"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CurrencyTransaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Reason"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.ToTable("CurrencyTransactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CustomReaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("IsRegex"); + + b.Property("OwnerOnly"); + + b.Property("Response"); + + b.Property("Trigger"); + + b.HasKey("Id"); + + b.ToTable("CustomReactions"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Donator", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Amount"); + + b.Property("Name"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Donators"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Text"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("EightBallResponses"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildConfigId1"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.HasIndex("GuildConfigId1"); + + b.ToTable("FilterChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildConfigId"); + + b.Property("Word"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FilteredWord"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.Property("GuildId"); + + b.Property("Type"); + + b.Property("Username"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("FollowedStream"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildConfigId"); + + b.HasKey("Id"); + + b.HasIndex("GuildConfigId"); + + b.ToTable("GCChannelId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoAssignRoleId"); + + b.Property("AutoDeleteByeMessages"); + + b.Property("AutoDeleteByeMessagesTimer"); + + b.Property("AutoDeleteGreetMessages"); + + b.Property("AutoDeleteGreetMessagesTimer"); + + b.Property("AutoDeleteSelfAssignedRoleMessages"); + + b.Property("ByeMessageChannelId"); + + b.Property("ChannelByeMessageText"); + + b.Property("ChannelGreetMessageText"); + + b.Property("CleverbotEnabled"); + + b.Property("DefaultMusicVolume"); + + b.Property("DeleteMessageOnCommand"); + + b.Property("DmGreetMessageText"); + + b.Property("ExclusiveSelfAssignedRoles"); + + b.Property("FilterInvites"); + + b.Property("FilterWords"); + + b.Property("GreetMessageChannelId"); + + b.Property("GuildId"); + + b.Property("LogSettingId"); + + b.Property("MuteRoleName"); + + b.Property("PermissionRole"); + + b.Property("RootPermissionId"); + + b.Property("SendChannelByeMessage"); + + b.Property("SendChannelGreetMessage"); + + b.Property("SendDmGreetMessage"); + + b.Property("VerbosePermissions"); + + b.Property("VoicePlusTextEnabled"); + + b.HasKey("Id"); + + b.HasIndex("GuildId") + .IsUnique(); + + b.HasIndex("LogSettingId"); + + b.HasIndex("RootPermissionId"); + + b.ToTable("GuildConfigs"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredLogChannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("LogSettingId"); + + b.HasKey("Id"); + + b.HasIndex("LogSettingId"); + + b.ToTable("IgnoredVoicePresenceCHannels"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.LogSetting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelCreated"); + + b.Property("ChannelDestroyed"); + + b.Property("ChannelId"); + + b.Property("ChannelUpdated"); + + b.Property("IsLogging"); + + b.Property("LogUserPresence"); + + b.Property("LogVoicePresence"); + + b.Property("MessageDeleted"); + + b.Property("MessageUpdated"); + + b.Property("UserBanned"); + + b.Property("UserJoined"); + + b.Property("UserLeft"); + + b.Property("UserPresenceChannelId"); + + b.Property("UserUnbanned"); + + b.Property("UserUpdated"); + + b.Property("VoicePresenceChannelId"); + + b.HasKey("Id"); + + b.ToTable("LogSettings"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("ModuleName"); + + b.Property("Prefix"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("ModulePrefixes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.MusicPlaylist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author"); + + b.Property("AuthorId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("MusicPlaylists"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("NextId"); + + b.Property("PrimaryTarget"); + + b.Property("PrimaryTargetId"); + + b.Property("SecondaryTarget"); + + b.Property("SecondaryTargetName"); + + b.Property("State"); + + b.HasKey("Id"); + + b.HasIndex("NextId") + .IsUnique(); + + b.ToTable("Permission"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Status"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("PlayingStatus"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("MusicPlaylistId"); + + b.Property("Provider"); + + b.Property("ProviderType"); + + b.Property("Query"); + + b.Property("Title"); + + b.Property("Uri"); + + b.HasKey("Id"); + + b.HasIndex("MusicPlaylistId"); + + b.ToTable("PlaylistSong"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Quote", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuthorId"); + + b.Property("AuthorName") + .IsRequired(); + + b.Property("GuildId"); + + b.Property("Keyword") + .IsRequired(); + + b.Property("Text") + .IsRequired(); + + b.HasKey("Id"); + + b.ToTable("Quotes"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("BotConfigId"); + + b.Property("Icon"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("BotConfigId"); + + b.ToTable("RaceAnimals"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("IsPrivate"); + + b.Property("Message"); + + b.Property("ServerId"); + + b.Property("UserId"); + + b.Property("When"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Repeater", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChannelId"); + + b.Property("GuildId"); + + b.Property("Interval"); + + b.Property("Message"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId") + .IsUnique(); + + b.ToTable("Repeaters"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.SelfAssignedRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("GuildId"); + + b.Property("RoleId"); + + b.HasKey("Id"); + + b.HasIndex("GuildId", "RoleId") + .IsUnique(); + + b.ToTable("SelfAssignableRoles"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.BlacklistItem", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("Blacklist") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ClashCaller", b => + { + b.HasOne("NadekoBot.Services.Database.Models.ClashWar", "ClashWar") + .WithMany("Bases") + .HasForeignKey("ClashWarId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.CommandCooldown", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("CommandCooldowns") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.EightBallResponse", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("EightBallResponses") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilterChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterInvitesChannelIds") + .HasForeignKey("GuildConfigId"); + + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilterWordsChannelIds") + .HasForeignKey("GuildConfigId1"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FilteredWord", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FilteredWords") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.FollowedStream", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("FollowedStreams") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GCChannelId", b => + { + b.HasOne("NadekoBot.Services.Database.Models.GuildConfig") + .WithMany("GenerateCurrencyChannelIds") + .HasForeignKey("GuildConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.GuildConfig", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany() + .HasForeignKey("LogSettingId"); + + b.HasOne("NadekoBot.Services.Database.Models.Permission", "RootPermission") + .WithMany() + .HasForeignKey("RootPermissionId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredLogChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredChannels") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.IgnoredVoicePresenceChannel", b => + { + b.HasOne("NadekoBot.Services.Database.Models.LogSetting", "LogSetting") + .WithMany("IgnoredVoicePresenceChannelIds") + .HasForeignKey("LogSettingId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.ModulePrefix", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("ModulePrefixes") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.Permission", b => + { + b.HasOne("NadekoBot.Services.Database.Models.Permission", "Next") + .WithOne("Previous") + .HasForeignKey("NadekoBot.Services.Database.Models.Permission", "NextId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlayingStatus", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RotatingStatusMessages") + .HasForeignKey("BotConfigId"); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.PlaylistSong", b => + { + b.HasOne("NadekoBot.Services.Database.Models.MusicPlaylist") + .WithMany("Songs") + .HasForeignKey("MusicPlaylistId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("NadekoBot.Services.Database.Models.RaceAnimal", b => + { + b.HasOne("NadekoBot.Services.Database.Models.BotConfig") + .WithMany("RaceAnimals") + .HasForeignKey("BotConfigId"); + }); + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Administration.cs b/src/NadekoBot/Modules/Administration/Administration.cs new file mode 100644 index 00000000..0ca8d78a --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Administration.cs @@ -0,0 +1,920 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Extensions; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NadekoBot.Services; +using NadekoBot.Attributes; +using Discord.WebSocket; +using NadekoBot.Services.Database.Models; +using System.Net.Http; +using System.IO; +using static NadekoBot.Modules.Permissions.Permissions; +using System.Collections.Concurrent; + +namespace NadekoBot.Modules.Administration +{ + [NadekoModule("Administration", ".")] + public partial class Administration : DiscordModule + { + + private static ConcurrentDictionary GuildMuteRoles { get; } = new ConcurrentDictionary(); + + public Administration(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + NadekoBot.CommandHandler.CommandExecuted += DelMsgOnCmd_Handler; + } + + static Administration() + { + using (var uow = DbHandler.UnitOfWork()) + { + var configs = NadekoBot.AllGuildConfigs; + GuildMuteRoles = new ConcurrentDictionary(configs + .Where(c=>!string.IsNullOrWhiteSpace(c.MuteRoleName)) + .ToDictionary(c => c.GuildId, c => c.MuteRoleName)); + } + } + + private async void DelMsgOnCmd_Handler(object sender, CommandExecutedEventArgs e) + { + try + { + var channel = e.Message.Channel as ITextChannel; + if (channel == null) + return; + + //todo cache this + bool shouldDelete; + using (var uow = DbHandler.UnitOfWork()) + { + shouldDelete = uow.GuildConfigs.For(channel.Guild.Id).DeleteMessageOnCommand; + } + + if (shouldDelete) + await e.Message.DeleteAsync().ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex, "Delmsgoncmd errored..."); + } + } + + private static async Task GetMuteRole(IGuild guild) + { + const string defaultMuteRoleName = "nadeko-mute"; + + var muteRoleName = GuildMuteRoles.GetOrAdd(guild.Id, defaultMuteRoleName); + + var muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName); + if (muteRole == null) + { + + //if it doesn't exist, create it + try { muteRole = await guild.CreateRoleAsync(muteRoleName, GuildPermissions.None).ConfigureAwait(false); } + catch + { + //if creations fails, maybe the name is not correct, find default one, if doesn't work, create default one + muteRole = guild.Roles.FirstOrDefault(r => r.Name == muteRoleName) ?? + await guild.CreateRoleAsync(defaultMuteRoleName, GuildPermissions.None).ConfigureAwait(false); + } + + foreach (var toOverwrite in guild.GetTextChannels()) + { + try + { + await toOverwrite.AddPermissionOverwriteAsync(muteRole, new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny)) + .ConfigureAwait(false); + } + catch { } + await Task.Delay(200).ConfigureAwait(false); + } + } + return muteRole; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + public async Task ResetPermissions(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.PermissionsFor(channel.Guild.Id); + config.RootPermission = Permission.GetDefaultRoot(); + var toAdd = new PermissionCache() + { + RootPermission = config.RootPermission, + PermRole = config.PermissionRole, + Verbose = config.VerbosePermissions, + }; + Permissions.Permissions.Cache.AddOrUpdate(channel.Guild.Id, + toAdd, (id, old) => toAdd); + await uow.CompleteAsync(); + } + + await channel.SendMessageAsync($"{imsg.Author.Mention} 🆗 **Permissions for this server are reset.**"); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + public async Task Delmsgoncmd(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + GuildConfig conf; + using (var uow = DbHandler.UnitOfWork()) + { + conf = uow.GuildConfigs.For(channel.Guild.Id); + conf.DeleteMessageOnCommand = !conf.DeleteMessageOnCommand; + uow.GuildConfigs.Update(conf); + await uow.CompleteAsync(); + } + if (conf.DeleteMessageOnCommand) + await channel.SendMessageAsync("✅ **Now automatically deleting successful command invokations.**").ConfigureAwait(false); + else + await channel.SendMessageAsync("❗**Stopped automatic deletion of successful command invokations.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task Setrole(IUserMessage umsg, IGuildUser usr, [Remainder] IRole role) + { + var channel = (ITextChannel)umsg.Channel; + try + { + await usr.AddRolesAsync(role).ConfigureAwait(false); + await channel.SendMessageAsync($"ℹ️ Successfully added role **{role.Name}** to user **{usr.Username}**").ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync("⚠️ Failed to add role. **Bot has insufficient permissions.**\n").ConfigureAwait(false); + Console.WriteLine(ex.ToString()); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task Removerole(IUserMessage umsg, IGuildUser usr, [Remainder] IRole role) + { + var channel = (ITextChannel)umsg.Channel; + try + { + await usr.RemoveRolesAsync(role).ConfigureAwait(false); + await channel.SendMessageAsync($"ℹ️ Successfully removed role **{role.Name}** from user **{usr.Username}**").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ Failed to remove role. Most likely reason: **Insufficient permissions.**").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task RenameRole(IUserMessage umsg, IRole roleToEdit, string newname) + { + var channel = (ITextChannel)umsg.Channel; + try + { + if (roleToEdit.Position > (await channel.Guild.GetCurrentUserAsync().ConfigureAwait(false)).Roles.Max(r => r.Position)) + { + await channel.SendMessageAsync("🚫 You can't edit roles higher than your highest role.").ConfigureAwait(false); + return; + } + await roleToEdit.ModifyAsync(g => g.Name = newname).ConfigureAwait(false); + await channel.SendMessageAsync("✅ Role renamed.").ConfigureAwait(false); + } + catch (Exception) + { + await channel.SendMessageAsync("⚠️ Failed to rename role. Probably **insufficient permissions.**").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task RemoveAllRoles(IUserMessage umsg, [Remainder] IGuildUser user) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + await user.RemoveRolesAsync(user.Roles).ConfigureAwait(false); + await channel.SendMessageAsync($"🗑 Successfully removed **all** roles from user **{user.Username}**").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ Failed to remove roles. Most likely reason: **Insufficient permissions.**").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task CreateRole(IUserMessage umsg, [Remainder] string roleName = null) + { + var channel = (ITextChannel)umsg.Channel; + + + if (string.IsNullOrWhiteSpace(roleName)) + return; + try + { + var r = await channel.Guild.CreateRoleAsync(roleName).ConfigureAwait(false); + await channel.SendMessageAsync($"✅ Successfully created role **{r.Name}**.").ConfigureAwait(false); + } + catch (Exception) + { + await channel.SendMessageAsync("⚠️ Unspecified error.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task RoleColor(IUserMessage umsg, params string[] args) + { + var channel = (ITextChannel)umsg.Channel; + + if (args.Count() != 2 && args.Count() != 4) + { + await channel.SendMessageAsync("❌ The parameters specified are **invalid.**").ConfigureAwait(false); + return; + } + var roleName = args[0].ToUpperInvariant(); + var role = channel.Guild.Roles.Where(r=>r.Name.ToUpperInvariant() == roleName).FirstOrDefault(); + + if (role == null) + { + await channel.SendMessageAsync("🚫 That role **does not exist.**").ConfigureAwait(false); + return; + } + try + { + var rgb = args.Count() == 4; + var arg1 = args[1].Replace("#", ""); + + var red = Convert.ToByte(rgb ? int.Parse(arg1) : Convert.ToInt32(arg1.Substring(0, 2), 16)); + var green = Convert.ToByte(rgb ? int.Parse(args[2]) : Convert.ToInt32(arg1.Substring(2, 2), 16)); + var blue = Convert.ToByte(rgb ? int.Parse(args[3]) : Convert.ToInt32(arg1.Substring(4, 2), 16)); + + await role.ModifyAsync(r => r.Color = new Discord.Color(red, green, blue).RawValue).ConfigureAwait(false); + await channel.SendMessageAsync($"☑️ Role **{role.Name}'s** color has been changed.").ConfigureAwait(false); + } + catch (Exception) + { + await channel.SendMessageAsync("⚠️ Error occured, most likely **invalid parameters** or **insufficient permissions.**").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.BanMembers)] + public async Task Ban(IUserMessage umsg, IGuildUser user, [Remainder] string msg = null) + { + var channel = (ITextChannel)umsg.Channel; + if (string.IsNullOrWhiteSpace(msg)) + { + msg = "❗️No reason provided."; + } + if (umsg.Author.Id != user.Guild.OwnerId && user.Roles.Select(r=>r.Position).Max() >= ((IGuildUser)umsg.Author).Roles.Select(r => r.Position).Max()) + { + await channel.SendMessageAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy."); + return; + } + try + { + await (await user.CreateDMChannelAsync()).SendMessageAsync($"⛔️ **You have been BANNED from `{channel.Guild.Name}` server.**\n" + + $"⚖ *Reason:* {msg}").ConfigureAwait(false); + await Task.Delay(2000).ConfigureAwait(false); + } + catch { } + try + { + await channel.Guild.AddBanAsync(user, 7).ConfigureAwait(false); + + await channel.SendMessageAsync("⛔️ **Banned** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ **Error.** Most likely I don't have sufficient permissions.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.KickMembers)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Softban(IUserMessage umsg, IGuildUser user, [Remainder] string msg = null) + { + var channel = (ITextChannel)umsg.Channel; + if (string.IsNullOrWhiteSpace(msg)) + { + msg = "❗️No reason provided."; + } + if (umsg.Author.Id != user.Guild.OwnerId && user.Roles.Select(r => r.Position).Max() >= ((IGuildUser)umsg.Author).Roles.Select(r => r.Position).Max()) + { + await channel.SendMessageAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy."); + return; + } + try + { + await user.SendMessageAsync($"☣ **You have been SOFT-BANNED from `{channel.Guild.Name}` server.**\n" + + $"⚖ *Reason:* {msg}").ConfigureAwait(false); + await Task.Delay(2000).ConfigureAwait(false); + } + catch { } + try + { + await channel.Guild.AddBanAsync(user, 7).ConfigureAwait(false); + try { await channel.Guild.RemoveBanAsync(user).ConfigureAwait(false); } + catch { await channel.Guild.RemoveBanAsync(user).ConfigureAwait(false); } + + await channel.SendMessageAsync("☣ **Soft-Banned** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.KickMembers)] + public async Task Kick(IUserMessage umsg, IGuildUser user, [Remainder] string msg = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (user == null) + { + await channel.SendMessageAsync("❗️User not found.").ConfigureAwait(false); + return; + } + + if (umsg.Author.Id != user.Guild.OwnerId && user.Roles.Select(r => r.Position).Max() >= ((IGuildUser)umsg.Author).Roles.Select(r => r.Position).Max()) + { + await channel.SendMessageAsync("⚠️ You can't use this command on users with a role higher or equal to yours in the role hierarchy."); + return; + } + if (!string.IsNullOrWhiteSpace(msg)) + { + try + { + await user.SendMessageAsync($"‼️**You have been KICKED from `{channel.Guild.Name}` server.**\n" + + $"⚖ *Reason:* {msg}").ConfigureAwait(false); + await Task.Delay(2000).ConfigureAwait(false); + } + catch { } + } + try + { + await user.KickAsync().ConfigureAwait(false); + await channel.SendMessageAsync("‼️**Kicked** user **" + user.Username + "** ID: `" + user.Id + "`").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ Error. Most likely I don't have sufficient permissions.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + [Priority(1)] + public async Task SetMuteRole(IUserMessage imsg, [Remainder] string name) + { + var channel = (ITextChannel)imsg.Channel; + name = name.Trim(); + if (string.IsNullOrWhiteSpace(name)) + return; + + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + config.MuteRoleName = name; + GuildMuteRoles.AddOrUpdate(channel.Guild.Id, name, (id, old) => name); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync("☑️ **New mute role set.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + [Priority(0)] + public Task SetMuteRole(IUserMessage imsg, [Remainder] IRole role) + => SetMuteRole(imsg, role.Name); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + [RequirePermission(GuildPermission.MuteMembers)] + public async Task Mute(IUserMessage umsg, IGuildUser user) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + await user.ModifyAsync(usr => usr.Mute = true).ConfigureAwait(false); + await user.AddRolesAsync(await GetMuteRole(channel.Guild).ConfigureAwait(false)).ConfigureAwait(false); + await channel.SendMessageAsync($"🔇 **{user}** has been **muted** from text and voice chat successfully.").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + [RequirePermission(GuildPermission.MuteMembers)] + public async Task Unmute(IUserMessage umsg, IGuildUser user) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + await user.ModifyAsync(usr => usr.Mute = false).ConfigureAwait(false); + await user.RemoveRolesAsync(await GetMuteRole(channel.Guild).ConfigureAwait(false)).ConfigureAwait(false); + await channel.SendMessageAsync($"🔉 **{user}** has been **unmuted** from text and voice chat successfully.").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task ChatMute(IUserMessage umsg, IGuildUser user) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + await user.AddRolesAsync(await GetMuteRole(channel.Guild).ConfigureAwait(false)).ConfigureAwait(false); + await channel.SendMessageAsync($"✏️🚫 **{user}** has been **muted** from chatting successfully.").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task ChatUnmute(IUserMessage umsg, IGuildUser user) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + await user.RemoveRolesAsync(await GetMuteRole(channel.Guild).ConfigureAwait(false)).ConfigureAwait(false); + await channel.SendMessageAsync($"✏️✅ **{user}** has been **unmuted** from chatting successfully.").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.MuteMembers)] + public async Task VoiceMute(IUserMessage umsg, IGuildUser user) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + await user.ModifyAsync(usr => usr.Mute = true).ConfigureAwait(false); + await channel.SendMessageAsync($"🎙🚫 **{user}** has been **voice muted** successfully.").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.MuteMembers)] + public async Task VoiceUnmute(IUserMessage umsg, IGuildUser user) + { + var channel = (ITextChannel)umsg.Channel; + try + { + await user.ModifyAsync(usr => usr.Mute = false).ConfigureAwait(false); + await channel.SendMessageAsync($"🎙✅ **{user}** has been **voice unmuted** successfully.").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.DeafenMembers)] + public async Task Deafen(IUserMessage umsg, params IGuildUser[] users) + { + var channel = (ITextChannel)umsg.Channel; + + if (!users.Any()) + return; + try + { + foreach (var u in users) + { + await u.ModifyAsync(usr=>usr.Deaf = true).ConfigureAwait(false); + } + await channel.SendMessageAsync("🔇 **Deafen** successful.").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); + } + + } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.DeafenMembers)] + public async Task UnDeafen(IUserMessage umsg, params IGuildUser[] users) + { + var channel = (ITextChannel)umsg.Channel; + + if (!users.Any()) + return; + try + { + foreach (var u in users) + { + await u.ModifyAsync(usr=> usr.Deaf = false).ConfigureAwait(false); + } + await channel.SendMessageAsync("🔊 **Undeafen** successful.").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync("⚠️ I most likely don't have the permission necessary for that.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageChannels)] + public async Task DelVoiChanl(IUserMessage umsg, [Remainder] IVoiceChannel voiceChannel) + { + await voiceChannel.DeleteAsync().ConfigureAwait(false); + await umsg.Channel.SendMessageAsync($"🗑 Removed voice channel **{voiceChannel.Name}** successfully.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageChannels)] + public async Task CreatVoiChanl(IUserMessage umsg, [Remainder] string channelName) + { + var channel = (ITextChannel)umsg.Channel; + var ch = await channel.Guild.CreateVoiceChannelAsync(channelName).ConfigureAwait(false); + await channel.SendMessageAsync($"✅ Created voice channel **{ch.Name}**. ID: `{ch.Id}`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageChannels)] + public async Task DelTxtChanl(IUserMessage umsg, [Remainder] ITextChannel toDelete) + { + await toDelete.DeleteAsync().ConfigureAwait(false); + await umsg.Channel.SendMessageAsync($"🗑 Removed text channel **{toDelete.Name}**. ID: `{toDelete.Id}`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageChannels)] + public async Task CreaTxtChanl(IUserMessage umsg, [Remainder] string channelName) + { + var channel = (ITextChannel)umsg.Channel; + var txtCh = await channel.Guild.CreateTextChannelAsync(channelName).ConfigureAwait(false); + await channel.SendMessageAsync($"✅ Added text channel **{txtCh.Name}**. ID: `{txtCh.Id}`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageChannels)] + public async Task SetTopic(IUserMessage umsg, [Remainder] string topic = null) + { + var channel = (ITextChannel)umsg.Channel; + topic = topic ?? ""; + await channel.ModifyAsync(c => c.Topic = topic); + await channel.SendMessageAsync("🆗 **New channel topic set.**").ConfigureAwait(false); + + } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageChannels)] + public async Task SetChanlName(IUserMessage umsg, [Remainder] string name) + { + var channel = (ITextChannel)umsg.Channel; + + await channel.ModifyAsync(c => c.Name = name).ConfigureAwait(false); + await channel.SendMessageAsync("🆗 **New channel name set.**").ConfigureAwait(false); + } + + + //delets her own messages, no perm required + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Prune(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + var user = channel.Guild.GetCurrentUser(); + + var enumerable = (await umsg.Channel.GetMessagesAsync()).AsEnumerable(); + enumerable = enumerable.Where(x => x.Author.Id == user.Id); + await umsg.Channel.DeleteMessagesAsync(enumerable); + } + + // prune x + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(ChannelPermission.ManageMessages)] + public async Task Prune(IUserMessage msg, int count) + { + var channel = (ITextChannel)msg.Channel; + await (msg as IUserMessage).DeleteAsync(); + int limit = (count < 100) ? count : 100; + var enumerable = (await msg.Channel.GetMessagesAsync(limit: limit)); + await msg.Channel.DeleteMessagesAsync(enumerable); + } + + //prune @user [x] + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(ChannelPermission.ManageMessages)] + public async Task Prune(IUserMessage msg, IGuildUser user, int count = 100) + { + var channel = (ITextChannel)msg.Channel; + int limit = (count < 100) ? count : 100; + var enumerable = (await msg.Channel.GetMessagesAsync(limit: limit)).Where(m => m.Author == user); + await msg.Channel.DeleteMessagesAsync(enumerable); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Die(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + try { await channel.SendMessageAsync("ℹ️ **Shutting down.**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + await Task.Delay(2000).ConfigureAwait(false); + Environment.Exit(0); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Setname(IUserMessage umsg, [Remainder] string newName) + { + var channel = (ITextChannel)umsg.Channel; + if (string.IsNullOrWhiteSpace(newName)) + return; + + await (await NadekoBot.Client.GetCurrentUserAsync()).ModifyAsync(u => u.Username = newName).ConfigureAwait(false); + + await channel.SendMessageAsync($"ℹ️ Successfully changed name to **{newName}**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task SetAvatar(IUserMessage umsg, [Remainder] string img = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(img)) + return; + + using (var http = new HttpClient()) + { + using (var sr = await http.GetStreamAsync(img)) + { + var imgStream = new MemoryStream(); + await sr.CopyToAsync(imgStream); + imgStream.Position = 0; + + await (await NadekoBot.Client.GetCurrentUserAsync().ConfigureAwait(false)).ModifyAsync(u => u.Avatar = imgStream).ConfigureAwait(false); + } + } + + await channel.SendMessageAsync("🆒 **New avatar set.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task SetGame(IUserMessage umsg, [Remainder] string game = null) + { + var channel = (ITextChannel)umsg.Channel; + + game = game ?? ""; + + await NadekoBot.Client.SetGame(game).ConfigureAwait(false); + + await channel.SendMessageAsync("👾 **New game set.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task SetStream(IUserMessage umsg, string url, [Remainder] string name = null) + { + var channel = (ITextChannel)umsg.Channel; + + name = name ?? ""; + + await NadekoBot.Client.SetStream(name, url).ConfigureAwait(false); + + await channel.SendMessageAsync("ℹ️ **New stream set.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Send(IUserMessage umsg, string where, [Remainder] string msg = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(msg)) + return; + + var ids = where.Split('|'); + if (ids.Length != 2) + return; + var sid = ulong.Parse(ids[0]); + var server = NadekoBot.Client.GetGuilds().Where(s => s.Id == sid).FirstOrDefault(); + + if (server == null) + return; + + if (ids[1].ToUpperInvariant().StartsWith("C:")) + { + var cid = ulong.Parse(ids[1].Substring(2)); + var ch = server.GetTextChannels().Where(c => c.Id == cid).FirstOrDefault(); + if (ch == null) + { + return; + } + await ch.SendMessageAsync(msg).ConfigureAwait(false); + } + else if (ids[1].ToUpperInvariant().StartsWith("U:")) + { + var uid = ulong.Parse(ids[1].Substring(2)); + var user = server.GetUsers().Where(u => u.Id == uid).FirstOrDefault(); + if (user == null) + { + return; + } + await user.SendMessageAsync(msg).ConfigureAwait(false); + } + else + { + await channel.SendMessageAsync("⚠️ Invalid format.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Announce(IUserMessage umsg, [Remainder] string message) + { + var channel = (ITextChannel)umsg.Channel; + + var channels = await Task.WhenAll(_client.GetGuilds().Select(g => + g.GetDefaultChannelAsync() + )).ConfigureAwait(false); + + await Task.WhenAll(channels.Select(c => c.SendMessageAsync($"🆕 **Message from {umsg.Author} `(Bot Owner)`:** " + message))) + .ConfigureAwait(false); + + await channel.SendMessageAsync("🆗").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task SaveChat(IUserMessage umsg, int cnt) + { + var channel = (ITextChannel)umsg.Channel; + + ulong? lastmsgId = null; + var sb = new StringBuilder(); + var msgs = new List(cnt); + while (cnt > 0) + { + var dlcnt = cnt < 100 ? cnt : 100; + IReadOnlyCollection dledMsgs; + if (lastmsgId == null) + dledMsgs = await umsg.Channel.GetMessagesAsync(cnt).ConfigureAwait(false); + else + dledMsgs = await umsg.Channel.GetMessagesAsync(lastmsgId.Value, Direction.Before, dlcnt); + + if (!dledMsgs.Any()) + break; + + msgs.AddRange(dledMsgs); + lastmsgId = msgs[msgs.Count - 1].Id; + cnt -= 100; + } + var title = $"Chatlog-{channel.Guild.Name}/#{channel.Name}-{DateTime.Now}.txt"; + await (umsg.Author as IGuildUser).SendFileAsync( + await JsonConvert.SerializeObject(new { Messages = msgs.Select(s => $"【{s.Timestamp:HH:mm:ss}】{s.Author}:" + s.ToString()) }, Formatting.Indented).ToStream().ConfigureAwait(false), + title, title).ConfigureAwait(false); + } + + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.MentionEveryone)] + public async Task MentionRole(IUserMessage umsg, params IRole[] roles) + { + var channel = (ITextChannel)umsg.Channel; + + string send = $"❕{umsg.Author.Mention} __`has invoked a mention on the following roles`__❕"; + foreach (var role in roles) + { + send += $"\n**{role.Name}**\n"; + send += string.Join(", ", (await channel.Guild.GetUsersAsync()).Where(u => u.Roles.Contains(role)).Distinct().Select(u=>u.Mention)); + } + + while (send.Length > 2000) + { + var curstr = send.Substring(0, 2000); + await channel.SendMessageAsync(curstr.Substring(0, + curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1)).ConfigureAwait(false); + send = curstr.Substring(curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1) + + send.Substring(2000); + } + await channel.SendMessageAsync(send).ConfigureAwait(false); + } + + IGuild nadekoSupportServer; + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Donators(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + IEnumerable donatorsOrdered; + + using (var uow = DbHandler.UnitOfWork()) + { + donatorsOrdered = uow.Donators.GetDonatorsOrdered(); + } + + string str = $"**Thanks to the people listed below for making this project happen!**\n"; + await channel.SendMessageAsync(str + string.Join("⭐", donatorsOrdered.Select(d => d.Name))).ConfigureAwait(false); + + nadekoSupportServer = nadekoSupportServer ?? NadekoBot.Client.GetGuild(117523346618318850); + + if (nadekoSupportServer == null) + return; + + var patreonRole = nadekoSupportServer.GetRole(236667642088259585); + if (patreonRole == null) + return; + + var usrs = nadekoSupportServer.GetUsers().Where(u => u.Roles.Contains(patreonRole)); + await channel.SendMessageAsync("\n`Patreon supporters:`\n" + string.Join("⭐", usrs.Select(d => d.Username))).ConfigureAwait(false); + } + + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Donadd(IUserMessage umsg, IUser donator, int amount) + { + var channel = (ITextChannel)umsg.Channel; + + Donator don; + using (var uow = DbHandler.UnitOfWork()) + { + don = uow.Donators.AddOrUpdateDonator(donator.Id, donator.Username, amount); + await uow.CompleteAsync(); + } + + await channel.SendMessageAsync($"Successfuly added a new donator. Total donated amount from this user: {don.Amount} 👑").ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/AntiRaidCommands.cs b/src/NadekoBot/Modules/Administration/Commands/AntiRaidCommands.cs new file mode 100644 index 00000000..06613873 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/AntiRaidCommands.cs @@ -0,0 +1,281 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + public enum PunishmentAction + { + Mute, + Kick, + Ban, + } + + public enum ProtectionType + { + Raiding, + Spamming, + } + + private class AntiRaidSetting + { + public int UserThreshold { get; set; } + public int Seconds { get; set; } + public PunishmentAction Action { get; set; } + public int UsersCount { get; set; } + public ConcurrentHashSet RaidUsers { get; set; } = new ConcurrentHashSet(); + } + + private class AntiSpamSetting + { + public PunishmentAction Action { get; set; } + public int MessageThreshold { get; set; } = 3; + public ConcurrentDictionary UserStats { get; set; } + = new ConcurrentDictionary(); + } + + private class UserSpamStats + { + public int Count { get; set; } + public string LastMessage { get; set; } + + public UserSpamStats(string msg) + { + Count = 1; + LastMessage = msg.ToUpperInvariant(); + } + + public void ApplyNextMessage(string message) + { + var upperMsg = message.ToUpperInvariant(); + if (upperMsg == LastMessage) + Count++; + else + { + LastMessage = upperMsg; + Count = 0; + } + } + } + + [Group] + public class AntiRaidCommands + { + private static ConcurrentDictionary antiRaidGuilds = + new ConcurrentDictionary(); + // guildId | (userId|messages) + private static ConcurrentDictionary antiSpamGuilds = + new ConcurrentDictionary(); + + private Logger _log { get; } + + public AntiRaidCommands(ShardedDiscordClient client) + { + _log = LogManager.GetCurrentClassLogger(); + + client.MessageReceived += (imsg) => + { + var msg = imsg as IUserMessage; + if (msg == null || msg.Author.IsBot) + return Task.CompletedTask; + + var channel = msg.Channel as ITextChannel; + if (channel == null) + return Task.CompletedTask; + + var t = Task.Run(async () => + { + try + { + AntiSpamSetting spamSettings; + if (!antiSpamGuilds.TryGetValue(channel.Guild.Id, out spamSettings)) + return; + + var stats = spamSettings.UserStats.AddOrUpdate(msg.Author.Id, new UserSpamStats(msg.Content), + (id, old) => { old.ApplyNextMessage(msg.Content); return old; }); + + if (stats.Count >= spamSettings.MessageThreshold) + { + if (spamSettings.UserStats.TryRemove(msg.Author.Id, out stats)) + { + await PunishUsers(spamSettings.Action, await GetMuteRole(channel.Guild), ProtectionType.Spamming, (IGuildUser)msg.Author) + .ConfigureAwait(false); + } + } + } + catch { } + }); + return Task.CompletedTask; + }; + + client.UserJoined += (usr) => + { + if (usr.IsBot) + return Task.CompletedTask; + + AntiRaidSetting settings; + if (!antiRaidGuilds.TryGetValue(usr.Guild.Id, out settings)) + return Task.CompletedTask; + + var t = Task.Run(async () => + { + if (!settings.RaidUsers.Add(usr)) + return; + + ++settings.UsersCount; + + if (settings.UsersCount >= settings.UserThreshold) + { + var users = settings.RaidUsers.ToArray(); + settings.RaidUsers.Clear(); + + await PunishUsers(settings.Action, await GetMuteRole(usr.Guild), ProtectionType.Raiding, users).ConfigureAwait(false); + } + await Task.Delay(1000 * settings.Seconds).ConfigureAwait(false); + + settings.RaidUsers.TryRemove(usr); + --settings.UsersCount; + }); + + return Task.CompletedTask; + }; + } + + private async Task PunishUsers(PunishmentAction action, IRole muteRole, ProtectionType pt, params IGuildUser[] gus) + { + foreach (var gu in gus) + { + switch (action) + { + case PunishmentAction.Mute: + try + { + await gu.AddRolesAsync(muteRole); + } + catch (Exception ex) { _log.Warn(ex, "I can't apply punishement"); } + break; + case PunishmentAction.Kick: + try + { + await gu.Guild.AddBanAsync(gu, 7); + try + { + await gu.Guild.RemoveBanAsync(gu); + } + catch + { + await gu.Guild.RemoveBanAsync(gu); + // try it twice, really don't want to ban user if + // only kick has been specified as the punishement + } + } + catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); } + break; + case PunishmentAction.Ban: + try + { + await gu.Guild.AddBanAsync(gu, 7); + } + catch (Exception ex) { _log.Warn(ex, "I can't apply punishment"); } + break; + default: + break; + } + } + await LogCommands.TriggeredAntiProtection(gus, action, pt).ConfigureAwait(false); + } + + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + public async Task AntiRaid(IUserMessage imsg, int userThreshold, int seconds, PunishmentAction action) + { + var channel = (ITextChannel)imsg.Channel; + + if (userThreshold < 2 || userThreshold > 30) + { + await channel.SendMessageAsync("❗️User threshold must be between **2** and **30**.").ConfigureAwait(false); + return; + } + + if (seconds < 2 || seconds > 300) + { + await channel.SendMessageAsync("❗️Time must be between **2** and **300** seconds.").ConfigureAwait(false); + return; + } + + try + { + await GetMuteRole(channel.Guild).ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync("⚠️ Failed creating a mute role. Give me ManageRoles permission" + + "or create 'nadeko-mute' role with disabled SendMessages and try again.") + .ConfigureAwait(false); + _log.Warn(ex); + return; + } + + var setting = new AntiRaidSetting() + { + Action = action, + Seconds = seconds, + UserThreshold = userThreshold, + }; + antiRaidGuilds.AddOrUpdate(channel.Guild.Id, setting, (id, old) => setting); + + await channel.SendMessageAsync($"ℹ️ {imsg.Author.Mention} If **{userThreshold}** or more users join within **{seconds}** seconds, I will **{action}** them.") + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + public async Task AntiSpam(IUserMessage imsg, int messageCount=3, PunishmentAction action = PunishmentAction.Mute) + { + var channel = (ITextChannel)imsg.Channel; + + if (messageCount < 2 || messageCount > 10) + return; + + AntiSpamSetting throwaway; + if (antiSpamGuilds.TryRemove(channel.Guild.Id, out throwaway)) + { + await channel.SendMessageAsync("🆗 **Anti-Spam feature** has been **disabled** on this server.").ConfigureAwait(false); + } + else + { + try + { + await GetMuteRole(channel.Guild).ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync("⚠️ Failed creating a mute role. Give me ManageRoles permission" + + "or create 'nadeko-mute' role with disabled SendMessages and try again.") + .ConfigureAwait(false); + _log.Warn(ex); + return; + } + + if (antiSpamGuilds.TryAdd(channel.Guild.Id, new AntiSpamSetting() + { + Action = action, + MessageThreshold = messageCount, + })) + await channel.SendMessageAsync("✅ **Anti-Spam feature** has been **enabled** on this server.").ConfigureAwait(false); + } + + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs b/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs new file mode 100644 index 00000000..54f73ae5 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/AutoAssignRoleCommands.cs @@ -0,0 +1,80 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class AutoAssignRoleCommands + { + private Logger _log { get; } + + public AutoAssignRoleCommands() + { + var _client = NadekoBot.Client; + this._log = LogManager.GetCurrentClassLogger(); + _client.UserJoined += (user) => + { + var t = Task.Run(async () => + { + try + { + GuildConfig conf; + using (var uow = DbHandler.UnitOfWork()) + { + conf = uow.GuildConfigs.For(user.Guild.Id); + } + + if (conf.AutoAssignRoleId == 0) + return; + + var role = user.Guild.Roles.FirstOrDefault(r => r.Id == conf.AutoAssignRoleId); + + if (role != null) + await user.AddRolesAsync(role); + } + catch (Exception ex) { _log.Warn(ex); } + }); + return Task.CompletedTask; + }; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task AutoAssignRole(IUserMessage umsg, [Remainder] IRole role = null) + { + var channel = (ITextChannel)umsg.Channel; + + GuildConfig conf; + using (var uow = DbHandler.UnitOfWork()) + { + conf = uow.GuildConfigs.For(channel.Guild.Id); + if (role == null) + conf.AutoAssignRoleId = 0; + else + conf.AutoAssignRoleId = role.Id; + + uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (role == null) + { + await channel.SendMessageAsync("🆗 **Auto assign role** on user join is now **disabled**.").ConfigureAwait(false); + return; + } + + await channel.SendMessageAsync("✅ **Auto assign role** on user join is now **enabled**.").ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs b/src/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs new file mode 100644 index 00000000..50dfaed7 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs @@ -0,0 +1,103 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class CrossServerTextChannel + { + public CrossServerTextChannel() + { + _log = LogManager.GetCurrentClassLogger(); + NadekoBot.Client.MessageReceived += (imsg) => + { + if (imsg.Author.IsBot) + return Task.CompletedTask; + + var msg = imsg as IUserMessage; + if (msg == null) + return Task.CompletedTask; + + var channel = imsg.Channel as ITextChannel; + if (channel == null) + return Task.CompletedTask; + + Task.Run(async () => + { + if (msg.Author.Id == NadekoBot.Client.GetCurrentUser().Id) return; + foreach (var subscriber in Subscribers) + { + var set = subscriber.Value; + if (!set.Contains(msg.Channel)) + continue; + foreach (var chan in set.Except(new[] { channel })) + { + try { await chan.SendMessageAsync(GetText(channel.Guild, channel, (IGuildUser)msg.Author, msg)).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + } + } + }); + return Task.CompletedTask; + }; + } + + private string GetText(IGuild server, ITextChannel channel, IGuildUser user, IUserMessage message) => + $"**{server.Name} | {channel.Name}** `{user.Username}`: " + message.Content; + + public static readonly ConcurrentDictionary> Subscribers = new ConcurrentDictionary>(); + private Logger _log { get; } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Scsc(IUserMessage msg) + { + var channel = (ITextChannel)msg.Channel; + var token = new NadekoRandom().Next(); + var set = new ConcurrentHashSet(); + if (Subscribers.TryAdd(token, set)) + { + set.Add(channel); + await ((IGuildUser)msg.Author).SendMessageAsync("This is your CSC token:" + token.ToString()).ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task Jcsc(IUserMessage imsg, int token) + { + var channel = (ITextChannel)imsg.Channel; + + ConcurrentHashSet set; + if (!Subscribers.TryGetValue(token, out set)) + return; + set.Add(channel); + await channel.SendMessageAsync(":ok:").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task Lcsc(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + foreach (var subscriber in Subscribers) + { + subscriber.Value.TryRemove(channel); + } + await channel.SendMessageAsync(":ok:").ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Commands/DMForwardCommands.cs b/src/NadekoBot/Modules/Administration/Commands/DMForwardCommands.cs new file mode 100644 index 00000000..2930ecea --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/DMForwardCommands.cs @@ -0,0 +1,86 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class DMForwardCommands + { + private static bool ForwardDMs { get; set; } + private static bool ForwardDMsToAllOwners { get; set; } + + static DMForwardCommands() + { + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.BotConfig.GetOrCreate(); + ForwardDMs = config.ForwardMessages; + ForwardDMsToAllOwners = config.ForwardToAllOwners; + } + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task ForwardMessages(IUserMessage imsg) + { + var channel = imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.BotConfig.GetOrCreate(); + ForwardDMs = config.ForwardMessages = !config.ForwardMessages; + uow.Complete(); + } + if (ForwardDMs) + await channel.SendMessageAsync("✅ **I will forward DMs from now on.**").ConfigureAwait(false); + else + await channel.SendMessageAsync("🆗 **I will stop forwarding DMs from now on.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task ForwardToAll(IUserMessage imsg) + { + var channel = imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.BotConfig.GetOrCreate(); + ForwardDMsToAllOwners = config.ForwardToAllOwners = !config.ForwardToAllOwners; + uow.Complete(); + } + if (ForwardDMsToAllOwners) + await channel.SendMessageAsync("ℹ️ **I will forward DMs to all owners.**").ConfigureAwait(false); + else + await channel.SendMessageAsync("ℹ️ **I will forward DMs only to the first owner.**").ConfigureAwait(false); + + } + + public static async Task HandleDMForwarding(IMessage msg, List ownerChannels) + { + if (ForwardDMs && ownerChannels.Any()) + { + var toSend = $"```markdown\n I received a message from [{msg.Author}]({msg.Author.Id}): {msg.Content}```"; + if (ForwardDMsToAllOwners) + { + var msgs = await Task.WhenAll(ownerChannels.Where(ch => ch.Recipient.Id != msg.Author.Id) + .Select(ch => ch.SendMessageAsync(toSend))).ConfigureAwait(false); + } + else + { + var firstOwnerChannel = ownerChannels.First(); + if (firstOwnerChannel.Recipient.Id != msg.Author.Id) + try { await firstOwnerChannel.SendMessageAsync(toSend).ConfigureAwait(false); } catch { } + } + } + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs new file mode 100644 index 00000000..42faf111 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -0,0 +1,739 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class LogCommands + { + private static ShardedDiscordClient _client { get; } + private static Logger _log { get; } + + private static string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; + + public static ConcurrentDictionary GuildLogSettings { get; } + + private static ConcurrentDictionary> UserPresenceUpdates { get; } = new ConcurrentDictionary>(); + private static Timer timerReference { get; } + private IGoogleApiService _google { get; } + + static LogCommands() + { + _client = NadekoBot.Client; + _log = LogManager.GetCurrentClassLogger(); + + using (var uow = DbHandler.UnitOfWork()) + { + GuildLogSettings = new ConcurrentDictionary(NadekoBot.AllGuildConfigs + .ToDictionary(g => g.GuildId, g => g.LogSetting)); + } + + timerReference = new Timer(async (state) => + { + try + { + var keys = UserPresenceUpdates.Keys.ToList(); + + await Task.WhenAll(keys.Select(async key => + { + List messages; + if (UserPresenceUpdates.TryRemove(key, out messages)) + try { await key.SendMessageAsync(string.Join(Environment.NewLine, messages)); } catch { } + })); + } + catch (Exception ex) + { + _log.Warn(ex); + } + }, null, TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(10)); + } + + public LogCommands(ShardedDiscordClient client) + { + //_client.MessageReceived += _client_MessageReceived; + _client.MessageUpdated += _client_MessageUpdated; + _client.MessageDeleted += _client_MessageDeleted; + _client.UserBanned += _client_UserBanned; + _client.UserUnbanned += _client_UserUnbanned; + _client.UserJoined += _client_UserJoined; + _client.UserLeft += _client_UserLeft; + _client.UserPresenceUpdated += _client_UserPresenceUpdated; + _client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated; + _client.UserUpdated += _client_UserUpdated; + + _client.ChannelCreated += _client_ChannelCreated; + _client.ChannelDestroyed += _client_ChannelDestroyed; + _client.ChannelUpdated += _client_ChannelUpdated; + } + + public static async Task TriggeredAntiProtection(IGuildUser[] users, PunishmentAction action, ProtectionType protection) + { + if (users.Length == 0) + return; + + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(users.First().Guild.Id, out logSetting) + || !logSetting.IsLogging) + return; + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(users.First().Guild, logSetting)) == null) + return; + + var punishment = ""; + if (action == PunishmentAction.Mute) + { + punishment = "🔇 MUTED"; + //punishment = "MUTED"; + } + else if (action == PunishmentAction.Kick) + { + punishment = "☣ SOFT-BANNED (KICKED)"; + //punishment = "KICKED"; + } + else if (action == PunishmentAction.Ban) + { + punishment = "⛔️ BANNED"; + //punishment = "BANNED"; + } + await logChannel.SendMessageAsync(String.Join("\n",users.Select(user=>$"‼️ {Format.Bold(user.ToString())} got **{punishment}** due to __**{protection}**__ protection on **{user.Guild.Name}** server."))) + //await logChannel.SendMessageAsync(String.Join("\n",users.Select(user=>$"{Format.Bold(user.ToString())} was **{punishment}** due to `{protection}` protection on **{user.Guild.Name}** server."))) + .ConfigureAwait(false); + } + + private Task _client_UserUpdated(IGuildUser before, IGuildUser after) + { + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(before.Guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.UserUpdated) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(before.Guild, logSetting)) == null) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try + { + string str = $"🕔`{prettyCurrentTime}`"; + if (before.Username != after.Username) + //str += $"**Name Changed**`{before.Username}#{before.Discriminator}`\n\t\t`New:`{after.ToString()}`"; + str += $"👤__**{before.Username}#{before.Discriminator}**__ **| Name Changed |** 🆔 `{before.Id}`\n\t\t`New:` **{after.ToString()}**"; + else if (before.Nickname != after.Nickname) + str += $"👤__**{before.Username}#{before.Discriminator}**__ **| Nickname Changed |** 🆔 `{before.Id}`\n\t\t`Old:` **{before.Nickname}#{before.Discriminator}**\n\t\t`New:` **{after.Nickname}#{after.Discriminator}**"; + //str += $"**Nickname Changed**`{before.Username}#{before.Discriminator}`\n\t\t`Old:` {before.Nickname}#{before.Discriminator}\n\t\t`New:` {after.Nickname}#{after.Discriminator}"; + else if (before.AvatarUrl != after.AvatarUrl) + //str += $"**Avatar Changed**👤`{before.Username}#{before.Discriminator}`\n\t {await _google.ShortenUrl(before.AvatarUrl)} `=>` {await _google.ShortenUrl(after.AvatarUrl)}"; + str += $"👤__**{before.Username}#{before.Discriminator}**__ **| Avatar Changed |** 🆔 `{before.Id}`\n\t🖼 {await _google.ShortenUrl(before.AvatarUrl)} `=>` {await _google.ShortenUrl(after.AvatarUrl)}"; + else if (!before.Roles.SequenceEqual(after.Roles)) + { + if (before.Roles.Count() < after.Roles.Count()) + { + var diffRoles = after.Roles.Where(r => !before.Roles.Contains(r)).Select(r => "**" + r.Name + "**"); + //str += $"**User's Roles changed ⚔➕**👤`{before.ToString()}`\n\tNow has {string.Join(", ", diffRoles)} role."; + str += $"👤__**{before.ToString()}**__ **| User's Role Added |** 🆔 `{before.Id}`\n\t✅ {string.Join(", ", diffRoles)}\n\t\t⚔ **`{string.Join(", ", after.Roles.Select(r => r.Name)).SanitizeMentions()}`** ⚔"; + } + else if (before.Roles.Count() > after.Roles.Count()) + { + var diffRoles = before.Roles.Where(r => !after.Roles.Contains(r)).Select(r => "**" + r.Name + "**"); + //str += $"**User's Roles changed **`{before.ToString()}`\n\tNo longer has {string.Join(", ", diffRoles)} role."; + str += $"👤__**{before.ToString()}**__ **| User's Role Removed |** 🆔 `{before.Id}`\n\t🚮 {string.Join(", ", diffRoles)}\n\t\t⚔ **`{string.Join(", ", after.Roles.Select(r => r.Name)).SanitizeMentions()}`** ⚔"; + } + } + else + return; + try { await logChannel.SendMessageAsync(str).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + } + catch { } + }); + + return Task.CompletedTask; + } + + private Task _client_ChannelUpdated(IChannel cbefore, IChannel cafter) + { + var before = cbefore as IGuildChannel; + if (before == null) + return Task.CompletedTask; + var after = (IGuildChannel)cafter; + + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(before.Guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.ChannelUpdated + || logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == after.Id)) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(before.Guild, logSetting)) == null) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try + { + if (before.Name != after.Name) + //await logChannel.SendMessageAsync($@"`{prettyCurrentTime}` **Channel Name Changed** `#{after.Name}` ({after.Id}) + await logChannel.SendMessageAsync($@"🕓`{prettyCurrentTime}`ℹ️ **| Channel Name Changed |** #⃣ `{after.Name} ({after.Id})` + `Old:` {before.Name} + **`New:`** {after.Name}").ConfigureAwait(false); + else if ((before as ITextChannel).Topic != (after as ITextChannel).Topic) + //await logChannel.SendMessageAsync($@"`{prettyCurrentTime}` **Channel Topic Changed** `#{after.Name}` ({after.Id}) + await logChannel.SendMessageAsync($@"🕘`{prettyCurrentTime}`ℹ️ **| Channel Topic Changed |** #⃣ `{after.Name} ({after.Id})` + `Old:` {((ITextChannel)before).Topic} + **`New:`** {((ITextChannel)after).Topic}").ConfigureAwait(false); + } + catch { } + }); + + return Task.CompletedTask; + } + + private Task _client_ChannelDestroyed(IChannel ich) + { + var ch = ich as IGuildChannel; + if (ch == null) + return Task.CompletedTask; + + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.ChannelDestroyed + || logSetting.IgnoredChannels.Any(ilc=>ilc.ChannelId == ch.Id)) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(ch.Guild, logSetting)) == null) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try { await logChannel.SendMessageAsync($"🕕`{prettyCurrentTime}`🗑 **| {(ch is IVoiceChannel ? "Voice" : "Text")} Channel Deleted #⃣ {ch.Name}** `({ch.Id})`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + }); + + return Task.CompletedTask; + } + + private Task _client_ChannelCreated(IChannel ich) + { + var ch = ich as IGuildChannel; + if (ch == null) + return Task.CompletedTask; + + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(ch.Guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.ChannelCreated) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(ch.Guild, logSetting)) == null) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try { await logChannel.SendMessageAsync($"🕓`{prettyCurrentTime}`🆕 **| {(ch is IVoiceChannel ? "Voice" : "Text")} Channel Created: #⃣ {ch.Name}** `({ch.Id})`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + }); + + return Task.CompletedTask; + } + + private Task _client_UserVoiceStateUpdated(IUser iusr, IVoiceState before, IVoiceState after) + { + var usr = iusr as IGuildUser; + if (usr == null) + return Task.CompletedTask; + + var beforeVch = before.VoiceChannel; + var afterVch = after.VoiceChannel; + + if (beforeVch == afterVch) + return Task.CompletedTask; + + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out logSetting) + || !logSetting.LogVoicePresence) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(usr.Guild, logSetting, LogChannelType.Voice)) == null) + return Task.CompletedTask; + + string str = null; + if (beforeVch?.Guild == afterVch?.Guild) + { + str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ moved from **{beforeVch.Name}** to **{afterVch.Name}** voice channel."; + } + else if (beforeVch == null) + { + str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ has joined **{afterVch.Name}** voice channel."; + } + else if (afterVch == null) + { + str = $"🎙`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__ has left **{beforeVch.Name}** voice channel."; + } + if(str != null) + UserPresenceUpdates.AddOrUpdate(logChannel, new List() { str }, (id, list) => { list.Add(str); return list; }); + + return Task.CompletedTask; + } + + private Task _client_UserPresenceUpdated(IGuildUser usr, IPresence before, IPresence after) + { + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out logSetting) + || !logSetting.LogUserPresence + || before.Status == after.Status) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(usr.Guild, logSetting, LogChannelType.UserPresence)) == null) + return Task.CompletedTask; + string str; + if (before.Status != after.Status) + str = $"🔵`{prettyCurrentTime}`👤__**{usr.Username}**__ is now **{after.Status}**."; + else + str = $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game}**."; + + UserPresenceUpdates.AddOrUpdate(logChannel, new List() { str }, (id, list) => { list.Add(str); return list; }); + + return Task.CompletedTask; + } + + private Task _client_UserLeft(IGuildUser usr) + { + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.UserLeft) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(usr.Guild, logSetting)) == null) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try { await logChannel.SendMessageAsync($"❗️🕛`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__❌ **| USER LEFT |** 🆔 `{usr.Id}`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + }); + + return Task.CompletedTask; + } + + private Task _client_UserJoined(IGuildUser usr) + { + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(usr.Guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.UserJoined) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(usr.Guild, logSetting)) == null) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try { await logChannel.SendMessageAsync($"❕🕓`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__✅ **| USER JOINED |** 🆔 `{usr.Id}`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + }); + + return Task.CompletedTask; + } + + private Task _client_UserUnbanned(IUser usr, IGuild guild) + { + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.UserUnbanned) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(guild, logSetting)) == null) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try { await logChannel.SendMessageAsync($"❕🕘`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__♻️ **| USER UN-BANNED |** 🆔 `{usr.Id}`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + }); + + return Task.CompletedTask; + } + + private Task _client_UserBanned(IUser usr, IGuild guild) + { + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.UserBanned) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(guild, logSetting)) == null) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try { await logChannel.SendMessageAsync($"‼️🕕`{prettyCurrentTime}`👤__**{usr.Username}#{usr.Discriminator}**__🚫 **| USER BANNED |** 🆔 `{usr.Id}`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + }); + + return Task.CompletedTask; + } + + private Task _client_MessageDeleted(ulong arg1, Optional imsg) + { + var msg = (imsg.IsSpecified ? imsg.Value : null) as IUserMessage; + if (msg == null || msg.IsAuthor()) + return Task.CompletedTask; + + var channel = msg.Channel as ITextChannel; + if (channel == null) + return Task.CompletedTask; + + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.MessageDeleted + || logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == channel.Id)) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(channel.Guild, logSetting)) == null || logChannel.Id == msg.Id) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + try + { + var str = $@"🕔`{prettyCurrentTime}`👤__**{msg.Author.Username}#{msg.Author.Discriminator}**__ **| Deleted Message |** 🆔 `{msg.Author.Id}` #⃣ `{channel.Name}` +🗑 {msg.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator)}"; + if (msg.Attachments.Any()) + str += $"{Environment.NewLine}📎 {string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))}"; + await logChannel.SendMessageAsync(str.SanitizeMentions()).ConfigureAwait(false); + } + catch (Exception ex) { _log.Warn(ex); } + }); + + return Task.CompletedTask; + } + + private Task _client_MessageUpdated(Optional optmsg, IMessage imsg2) + { + var after = imsg2 as IUserMessage; + if (after == null || after.IsAuthor()) + return Task.CompletedTask; + + var before = (optmsg.IsSpecified ? optmsg.Value : null) as IUserMessage; + if (before == null) + return Task.CompletedTask; + + var channel = after.Channel as ITextChannel; + if (channel == null) + return Task.CompletedTask; + + LogSetting logSetting; + if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out logSetting) + || !logSetting.IsLogging + || !logSetting.MessageUpdated + || logSetting.IgnoredChannels.Any(ilc => ilc.ChannelId == channel.Id)) + return Task.CompletedTask; + + ITextChannel logChannel; + if ((logChannel = TryGetLogChannel(channel.Guild, logSetting)) == null || logChannel.Id == after.Channel.Id) + return Task.CompletedTask; + + var task = Task.Run(async () => + { + //try { await logChannel.SendMessageAsync($@"🕔`{prettyCurrentTime}` **Message** 📝 `#{channel.Name}` +//👤`{before.Author.Username}` + try { await logChannel.SendMessageAsync($@"🕔`{prettyCurrentTime}`👤__**{before.Author.Username}#{before.Author.Discriminator}**__ **| 📝 Edited Message |** 🆔 `{before.Author.Id}` #⃣ `{channel.Name}` + `Old:` {before.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator).SanitizeMentions()} + **`New:`** {after.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator).SanitizeMentions()}").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + }); + + return Task.CompletedTask; + } + +// private Task _client_MessageReceived(IMessage imsg) +// { +// var msg = imsg as IUserMessage; +// if (msg == null || msg.IsAuthor()) +// return Task.CompletedTask; + +// var channel = msg.Channel as ITextChannel; +// if (channel == null) +// return Task.CompletedTask; + +// LogSetting logSetting; +// if (!GuildLogSettings.TryGetValue(channel.Guild.Id, out logSetting) +// || !logSetting.IsLogging +// || !logSetting.MessageReceived) +// return Task.CompletedTask; + +// ITextChannel logChannel; +// if ((logChannel = TryGetLogChannel(channel.Guild, logSetting)) == null || logChannel.Id == imsg.Channel.Id) +// return Task.CompletedTask; + +// var task = Task.Run(async () => +// { +// var str = $@"🕔`{prettyCurrentTime}` **New Message** `#{channel.Name}` +//👤`{msg.Author.Username}`: {msg.Resolve(userHandling: UserMentionHandling.NameAndDiscriminator)}"; +// if (msg.Attachments.Any()) +// str += $"{Environment.NewLine}`Attachements`: {string.Join(", ", msg.Attachments.Select(a => a.ProxyUrl))}"; +// await logChannel.SendMessageAsync(str).ConfigureAwait(false); +// }); + +// return Task.CompletedTask; +// } + + private enum LogChannelType { Text, Voice, UserPresence }; + private static ITextChannel TryGetLogChannel(IGuild guild, LogSetting logSetting, LogChannelType logChannelType = LogChannelType.Text) + { + ulong id = 0; + switch (logChannelType) + { + case LogChannelType.Text: + id = logSetting.ChannelId; + break; + case LogChannelType.Voice: + id = logSetting.VoicePresenceChannelId; + break; + case LogChannelType.UserPresence: + id = logSetting.UserPresenceChannelId; + break; + } + var channel = guild.GetTextChannel(id); + + if (channel == null) + using (var uow = DbHandler.UnitOfWork()) + { + var newLogSetting = uow.GuildConfigs.For(guild.Id).LogSetting; + switch (logChannelType) + { + case LogChannelType.Text: + logSetting.IsLogging = false; + break; + case LogChannelType.Voice: + logSetting.LogVoicePresence = false; + break; + case LogChannelType.UserPresence: + logSetting.LogUserPresence = false; + break; + } + GuildLogSettings.AddOrUpdate(guild.Id, newLogSetting, (gid, old) => newLogSetting); + uow.Complete(); + return null; + } + else + return channel; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + [OwnerOnly] + public async Task LogServer(IUserMessage msg) + { + var channel = (ITextChannel)msg.Channel; + LogSetting logSetting; + using (var uow = DbHandler.UnitOfWork()) + { + logSetting = uow.GuildConfigs.For(channel.Guild.Id).LogSetting; + GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); + logSetting.IsLogging = !logSetting.IsLogging; + if (logSetting.IsLogging) + logSetting.ChannelId = channel.Id; + await uow.CompleteAsync(); + } + + if (logSetting.IsLogging) + await channel.SendMessageAsync("✅ **Logging enabled.**").ConfigureAwait(false); + else + await channel.SendMessageAsync("ℹ️ **Logging disabled.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + [OwnerOnly] + public async Task LogIgnore(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + int removed; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + LogSetting logSetting = GuildLogSettings.GetOrAdd(channel.Guild.Id, (id) => config.LogSetting); + removed = logSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == channel.Id); + config.LogSetting.IgnoredChannels.RemoveWhere(ilc => ilc.ChannelId == channel.Id); + if (removed == 0) + { + var toAdd = new IgnoredLogChannel { ChannelId = channel.Id }; + logSetting.IgnoredChannels.Add(toAdd); + config.LogSetting.IgnoredChannels.Add(toAdd); + } + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (removed == 0) + await channel.SendMessageAsync($"🆗 Logging will **now ignore** #⃣ `{channel.Name} ({channel.Id})`").ConfigureAwait(false); + else + await channel.SendMessageAsync($"ℹ️ Logging will **no longer ignore** #⃣ `{channel.Name} ({channel.Id})`").ConfigureAwait(false); + } + + //[LocalizedCommand, LocalizedRemarks, LocalizedSummary, LocalizedAlias] + //[RequireContext(ContextType.Guild)] + //[OwnerOnly] + //public async Task LogAdd(IUserMessage msg, [Remainder] string eventName) + //{ + // var channel = (ITextChannel)msg.Channel; + // //eventName = eventName?.Replace(" ","").ToLowerInvariant(); + + // switch (eventName.ToLowerInvariant()) + // { + // case "messageupdated": + // case "messagedeleted": + // case "userjoined": + // case "userleft": + // case "userbanned": + // case "userunbanned": + // case "channelcreated": + // case "channeldestroyed": + // case "channelupdated": + // using (var uow = DbHandler.UnitOfWork()) + // { + // var logSetting = uow.GuildConfigs.For(channel.Guild.Id).LogSetting; + // GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); + // var prop = logSetting.GetType().GetProperty(eventName); + // prop.SetValue(logSetting, true); + // await uow.CompleteAsync().ConfigureAwait(false); + // } + // await channel.SendMessageAsync($"`Now logging {eventName} event.`").ConfigureAwait(false); + // break; + // default: + // await channel.SendMessageAsync($"`Event \"{eventName}\" not found.`").ConfigureAwait(false); + // break; + // } + //} + + //[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias] + //[RequireContext(ContextType.Guild)] + //public async Task LogRemove(IUserMessage msg, string eventName) + //{ + // var channel = (ITextChannel)msg.Channel; + // eventName = eventName.ToLowerInvariant(); + + // switch (eventName) + // { + // case "messagereceived": + // case "messageupdated": + // case "messagedeleted": + // case "userjoined": + // case "userleft": + // case "userbanned": + // case "userunbanned": + // case "channelcreated": + // case "channeldestroyed": + // case "channelupdated": + // using (var uow = DbHandler.UnitOfWork()) + // { + // var config = uow.GuildConfigs.For(channel.Guild.Id); + // LogSetting logSetting = GuildLogSettings.GetOrAdd(channel.Guild.Id, (id) => config.LogSetting); + // logSetting.GetType().GetProperty(eventName).SetValue(logSetting, false); + // config.LogSetting = logSetting; + // await uow.CompleteAsync().ConfigureAwait(false); + // } + // await channel.SendMessageAsync($"`No longer logging {eventName} event.`").ConfigureAwait(false); + // break; + // default: + // await channel.SendMessageAsync($"`Event \"{eventName}\" not found.`").ConfigureAwait(false); + // break; + // } + //} + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + public async Task UserPresence(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var logSetting = uow.GuildConfigs.For(channel.Guild.Id).LogSetting; + GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); + enabled = logSetting.LogUserPresence = !logSetting.LogUserPresence; + if(enabled) + logSetting.UserPresenceChannelId = channel.Id; + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (enabled) + await channel.SendMessageAsync($"✅ Logging **user presence** updates in #⃣ `{channel.Name} ({channel.Id})`").ConfigureAwait(false); + else + await channel.SendMessageAsync($"ℹ️ Stopped logging **user presence** updates.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + public async Task VoicePresence(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var logSetting = uow.GuildConfigs.For(channel.Guild.Id).LogSetting; + GuildLogSettings.AddOrUpdate(channel.Guild.Id, (id) => logSetting, (id, old) => logSetting); + enabled = logSetting.LogVoicePresence = !logSetting.LogVoicePresence; + if (enabled) + logSetting.VoicePresenceChannelId = channel.Id; + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (enabled) + await channel.SendMessageAsync($"✅ Logging **voice presence** updates in #⃣ `{channel.Name} ({channel.Id})`").ConfigureAwait(false); + else + await channel.SendMessageAsync($"ℹ️ Stopped logging **voice presence** updates.").ConfigureAwait(false); + } + + //[LocalizedCommand, LocalizedDescription, LocalizedSummary, LocalizedAlias] + //[RequireContext(ContextType.Guild)] + //public async Task VoiPresIgnore(IUserMessage imsg, IVoiceChannel voiceChannel) + //{ + // var channel = (ITextChannel)imsg.Channel; + // int removed; + // using (var uow = DbHandler.UnitOfWork()) + // { + // var config = uow.GuildConfigs.For(channel.Guild.Id); + // LogSetting logSetting = GuildLogSettings.GetOrAdd(channel.Guild.Id, (id) => config.LogSetting); + // removed = logSetting.IgnoredVoicePresenceChannelIds.RemoveWhere(ivpc => ivpc.ChannelId == voiceChannel.Id); + // if (removed == 0) + // logSetting.IgnoredVoicePresenceChannelIds.Add(new IgnoredVoicePresenceChannel { ChannelId = voiceChannel.Id }); + // config.LogSetting = logSetting; + // await uow.CompleteAsync().ConfigureAwait(false); + // } + + // if (removed == 0) + // await channel.SendMessageAsync($"`Enabled logging voice presence updates for {voiceChannel.Name} ({voiceChannel.Id}) channel.`").ConfigureAwait(false); + // else + // await channel.SendMessageAsync($"`Disabled logging voice presence updates for {voiceChannel.Name} ({voiceChannel.Id}) channel.`").ConfigureAwait(false); + //} + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs b/src/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs new file mode 100644 index 00000000..953f2446 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs @@ -0,0 +1,166 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Attributes; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class RepeatCommands + { + public ConcurrentDictionary repeaters; + + public class RepeatRunner + { + private Logger _log { get; } + + private CancellationTokenSource source { get; set; } + private CancellationToken token { get; set; } + public Repeater Repeater { get; } + public ITextChannel Channel { get; } + + public RepeatRunner(Repeater repeater, ITextChannel channel = null) + { + _log = LogManager.GetCurrentClassLogger(); + this.Repeater = repeater; + this.Channel = channel ?? NadekoBot.Client.GetGuild(repeater.GuildId)?.GetTextChannel(repeater.ChannelId); + if (Channel == null) + return; + Task.Run(Run); + } + + + private async Task Run() + { + source = new CancellationTokenSource(); + token = source.Token; + IUserMessage oldMsg = null; + try + { + while (!token.IsCancellationRequested) + { + await Task.Delay(Repeater.Interval, token).ConfigureAwait(false); + if (oldMsg != null) + try { await oldMsg.DeleteAsync(); } catch { } + try { oldMsg = await Channel.SendMessageAsync("🔄 " + Repeater.Message).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); try { source.Cancel(); } catch { } } + } + } + catch (OperationCanceledException) { } + } + + public void Reset() + { + source.Cancel(); + var t = Task.Run(Run); + } + + public void Stop() + { + source.Cancel(); + } + } + + public RepeatCommands() + { + using (var uow = DbHandler.UnitOfWork()) + { + repeaters = new ConcurrentDictionary(uow.Repeaters.GetAll().Select(r => new RepeatRunner(r)).Where(r => r != null).ToDictionary(r => r.Repeater.ChannelId)); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task RepeatInvoke(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + RepeatRunner rep; + if (!repeaters.TryGetValue(channel.Id, out rep)) + { + await channel.SendMessageAsync("ℹ️ **No repeating message found on this server.**").ConfigureAwait(false); + return; + } + rep.Reset(); + await channel.SendMessageAsync("🔄 " + rep.Repeater.Message).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Repeat(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + RepeatRunner rep; + if (repeaters.TryRemove(channel.Id, out rep)) + { + using (var uow = DbHandler.UnitOfWork()) + { + uow.Repeaters.Remove(rep.Repeater); + await uow.CompleteAsync(); + } + rep.Stop(); + await channel.SendMessageAsync("✅ **Stopped repeating a message.**").ConfigureAwait(false); + } + else + await channel.SendMessageAsync("ℹ️ **No message is repeating.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Repeat(IUserMessage imsg, int minutes, [Remainder] string message) + { + var channel = (ITextChannel)imsg.Channel; + + if (minutes < 1 || minutes > 10080) + return; + + if (string.IsNullOrWhiteSpace(message)) + return; + + RepeatRunner rep; + + rep = repeaters.AddOrUpdate(channel.Id, (cid) => + { + using (var uow = DbHandler.UnitOfWork()) + { + var localRep = new Repeater + { + ChannelId = channel.Id, + GuildId = channel.Guild.Id, + Interval = TimeSpan.FromMinutes(minutes), + Message = message, + }; + uow.Repeaters.Add(localRep); + uow.Complete(); + return new RepeatRunner(localRep, channel); + } + }, (cid, old) => + { + using (var uow = DbHandler.UnitOfWork()) + { + old.Repeater.Message = message; + old.Repeater.Interval = TimeSpan.FromMinutes(minutes); + uow.Repeaters.Update(old.Repeater); + uow.Complete(); + } + old.Reset(); + return old; + }); + + await channel.SendMessageAsync($"🔁 Repeating **\"{rep.Repeater.Message}\"** every `{rep.Repeater.Interval.Days} day(s), {rep.Repeater.Interval.Hours} hour(s) and {rep.Repeater.Interval.Minutes} minute(s)`.").ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/Migration.cs b/src/NadekoBot/Modules/Administration/Commands/Migration.cs new file mode 100644 index 00000000..5ce821d9 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/Migration.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using Newtonsoft.Json; +using NLog; +using NadekoBot.Modules.Administration.Commands.Migration; +using System.Collections.Concurrent; +using NadekoBot.Extensions; +using NadekoBot.Services.Database; +using Microsoft.Data.Sqlite; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class Migration + { + private const int CURRENT_VERSION = 1; + + private Logger _log { get; } + + public Migration() + { + _log = LogManager.GetCurrentClassLogger(); + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public async Task MigrateData(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + var version = 0; + using (var uow = DbHandler.UnitOfWork()) + { + version = uow.BotConfig.GetOrCreate().MigrationVersion; + } + try + { + for (var i = version; i < CURRENT_VERSION; i++) + { + switch (i) + { + case 0: + Migrate0_9To1_0(); + break; + } + } + await umsg.Channel.SendMessageAsync("🆙 **Migration done.**").ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Error(ex); + await umsg.Channel.SendMessageAsync("⚠️ **Error while migrating, check `logs` for more informations.**").ConfigureAwait(false); + } + } + + private void Migrate0_9To1_0() + { + using (var uow = DbHandler.UnitOfWork()) + { + var botConfig = uow.BotConfig.GetOrCreate(); + MigrateConfig0_9(uow, botConfig); + MigratePermissions0_9(uow); + MigrateServerSpecificConfigs0_9(uow); + MigrateDb0_9(uow); + + //NOW save it + _log.Warn("Writing to disc"); + uow.Complete(); + botConfig.MigrationVersion = 1; + } + } + + private void MigrateDb0_9(IUnitOfWork uow) + { + var db = new SqliteConnection("Data Source=data/nadekobot.sqlite"); + + if (!File.Exists("data/nadekobot.sqlite")) + { + _log.Warn("No data from the old database will be migrated."); + return; + } + db.Open(); + + var com = db.CreateCommand(); + com.CommandText = "SELECT * FROM Announcement"; + + var reader = com.ExecuteReader(); + var i = 0; + while (reader.Read()) + { + var gid = (ulong)(long)reader["ServerId"]; + var greet = (long)reader["Greet"] == 1; + var greetDM = (long)reader["GreetPM"] == 1; + var greetChannel = (ulong)(long)reader["GreetChannelId"]; + var greetMsg = (string)reader["GreetText"]; + var bye = (long)reader["Bye"] == 1; + var byeDM = (long)reader["ByePM"] == 1; + var byeChannel = (ulong)(long)reader["ByeChannelId"]; + var byeMsg = (string)reader["ByeText"]; + var grdel = false; + var byedel = grdel; + var gc = uow.GuildConfigs.For(gid); + + if (greetDM) + gc.SendDmGreetMessage = greet; + else + gc.SendChannelGreetMessage = greet; + gc.GreetMessageChannelId = greetChannel; + gc.ChannelGreetMessageText = greetMsg; + + gc.SendChannelByeMessage = bye; + gc.ByeMessageChannelId = byeChannel; + gc.ChannelByeMessageText = byeMsg; + + gc.AutoDeleteGreetMessagesTimer = gc.AutoDeleteByeMessagesTimer = grdel ? 30 : 0; + _log.Info(++i); + } + + var com2 = db.CreateCommand(); + com.CommandText = "SELECT * FROM CurrencyState GROUP BY UserId"; + + i = 0; + var reader2 = com.ExecuteReader(); + while (reader2.Read()) + { + _log.Info(++i); + var curr = new Currency() + { + Amount = (long)reader2["Value"], + UserId = (ulong)(long)reader2["UserId"] + }; + uow.Currency.Add(curr); + } + db.Close(); + try { File.Move("data/nadekobot.sqlite", "data/DELETE_ME_nadekobot.sqlite"); } catch { } + } + + private void MigrateServerSpecificConfigs0_9(IUnitOfWork uow) + { + const string specificConfigsPath = "data/ServerSpecificConfigs.json"; + + if (!File.Exists(specificConfigsPath)) + { + _log.Warn($"No data from {specificConfigsPath} will be migrated."); + return; + } + + var configs = new ConcurrentDictionary(); + try + { + configs = JsonConvert + .DeserializeObject>( + File.ReadAllText(specificConfigsPath), new JsonSerializerSettings() + { + Error = (s, e) => + { + if (e.ErrorContext.Member.ToString() == "GenerateCurrencyChannels") + { + e.ErrorContext.Handled = true; + } + } + }); + } + catch (Exception ex) + { + _log.Warn(ex, "ServerSpecificConfig deserialization failed"); + return; + } + var i = 0; + var selfAssRoles = new ConcurrentHashSet(); + configs + .Select(p => new { data = p.Value, gconfig = uow.GuildConfigs.For(p.Key) }) + .AsParallel() + .ForAll(config => + { + try + { + var guildConfig = config.gconfig; + var data = config.data; + + guildConfig.AutoAssignRoleId = data.AutoAssignedRole; + guildConfig.DeleteMessageOnCommand = data.AutoDeleteMessagesOnCommand; + guildConfig.DefaultMusicVolume = data.DefaultMusicVolume; + guildConfig.ExclusiveSelfAssignedRoles = data.ExclusiveSelfAssignedRoles; + guildConfig.GenerateCurrencyChannelIds = new HashSet(data.GenerateCurrencyChannels.Select(gc => new GCChannelId() { ChannelId = gc.Key })); + selfAssRoles.AddRange(data.ListOfSelfAssignableRoles.Select(r => new SelfAssignedRole() { GuildId = guildConfig.GuildId, RoleId = r }).ToArray()); + var logSetting = guildConfig.LogSetting; + guildConfig.LogSetting.IsLogging = data.LogChannel != null; + guildConfig.LogSetting.ChannelId = data.LogChannel ?? 0; + guildConfig.LogSetting.IgnoredChannels = new HashSet(data.LogserverIgnoreChannels.Select(id => new IgnoredLogChannel() { ChannelId = id })); + + guildConfig.LogSetting.LogUserPresence = data.LogPresenceChannel != null; + guildConfig.LogSetting.UserPresenceChannelId = data.LogPresenceChannel ?? 0; + + + guildConfig.FollowedStreams = new HashSet(data.ObservingStreams.Select(x => + { + FollowedStream.FollowedStreamType type = FollowedStream.FollowedStreamType.Twitch; + switch (x.Type) + { + case StreamNotificationConfig0_9.StreamType.Twitch: + type = FollowedStream.FollowedStreamType.Twitch; + break; + case StreamNotificationConfig0_9.StreamType.Beam: + type = FollowedStream.FollowedStreamType.Beam; + break; + case StreamNotificationConfig0_9.StreamType.Hitbox: + type = FollowedStream.FollowedStreamType.Hitbox; + break; + default: + break; + } + + return new FollowedStream() + { + ChannelId = x.ChannelId, + GuildId = guildConfig.GuildId, + Username = x.Username.ToLowerInvariant(), + Type = type + }; + })); + guildConfig.VoicePlusTextEnabled = data.VoicePlusTextEnabled; + _log.Info("Migrating SpecificConfig for {0} done ({1})", guildConfig.GuildId, ++i); + } + catch (Exception ex) + { + _log.Error(ex); + } + }); + uow.SelfAssignedRoles.AddRange(selfAssRoles.ToArray()); + try { File.Move("data/ServerSpecificConfigs.json", "data/DELETE_ME_ServerSpecificCOnfigs.json"); } catch { } + } + + private void MigratePermissions0_9(IUnitOfWork uow) + { + var PermissionsDict = new ConcurrentDictionary(); + if (!Directory.Exists("data/permissions/")) + { + _log.Warn("No data from permissions will be migrated."); + return; + } + foreach (var file in Directory.EnumerateFiles("data/permissions/")) + { + try + { + var strippedFileName = Path.GetFileNameWithoutExtension(file); + if (string.IsNullOrWhiteSpace(strippedFileName)) continue; + var id = ulong.Parse(strippedFileName); + var data = JsonConvert.DeserializeObject(File.ReadAllText(file)); + PermissionsDict.TryAdd(id, data); + } + catch { } + } + var i = 0; + PermissionsDict + .Select(p => new { data = p.Value, gconfig = uow.GuildConfigs.For(p.Key) }) + .AsParallel() + .ForAll(perms => + { + try + { + var data = perms.data; + var gconfig = perms.gconfig; + + gconfig.PermissionRole = data.PermissionsControllerRole; + gconfig.VerbosePermissions = data.Verbose; + gconfig.FilteredWords = new HashSet(data.Words.Select(w => w.ToLowerInvariant()) + .Distinct() + .Select(w => new FilteredWord() { Word = w })); + gconfig.FilterWords = data.Permissions.FilterWords; + gconfig.FilterInvites = data.Permissions.FilterInvites; + + gconfig.FilterInvitesChannelIds = new HashSet(); + gconfig.FilterInvitesChannelIds.AddRange(data.ChannelPermissions.Where(kvp => kvp.Value.FilterInvites) + .Select(cp => new FilterChannelId() + { + ChannelId = cp.Key + })); + + gconfig.FilterWordsChannelIds = new HashSet(); + gconfig.FilterWordsChannelIds.AddRange(data.ChannelPermissions.Where(kvp => kvp.Value.FilterWords) + .Select(cp => new FilterChannelId() + { + ChannelId = cp.Key + })); + + gconfig.CommandCooldowns = new HashSet(data.CommandCooldowns + .Where(cc => !string.IsNullOrWhiteSpace(cc.Key) && cc.Value > 0) + .Select(cc => new CommandCooldown() + { + CommandName = cc.Key, + Seconds = cc.Value + })); + _log.Info("Migrating data from permissions folder for {0} done ({1})", gconfig.GuildId, ++i); + } + catch (Exception ex) + { + _log.Error(ex); + } + }); + + try { Directory.Move("data/permissions", "data/DELETE_ME_permissions"); } catch { } + + } + + private void MigrateConfig0_9(IUnitOfWork uow, BotConfig botConfig) + { + Config0_9 oldConfig; + const string configPath = "data/config.json"; + try + { + oldConfig = JsonConvert.DeserializeObject(File.ReadAllText(configPath)); + } + catch (FileNotFoundException) + { + _log.Warn("config.json not found"); + return; + } + catch (Exception) + { + _log.Error("Unknown error while deserializing file config.json, pls check its integrity, aborting migration"); + throw new MigrationException(); + } + + //Basic + botConfig.ForwardMessages = oldConfig.ForwardMessages; + botConfig.ForwardToAllOwners = oldConfig.ForwardToAllOwners; + botConfig.BufferSize = (ulong)oldConfig.BufferSize; + botConfig.RemindMessageFormat = oldConfig.RemindMessageFormat; + botConfig.CurrencySign = oldConfig.CurrencySign; + botConfig.CurrencyName = oldConfig.CurrencyName; + botConfig.DMHelpString = oldConfig.DMHelpString; + botConfig.HelpString = oldConfig.HelpString; + + //messages + botConfig.RotatingStatuses = oldConfig.IsRotatingStatus; + var messages = new List(); + + oldConfig.RotatingStatuses.ForEach(i => messages.Add(new PlayingStatus { Status = i })); + botConfig.RotatingStatusMessages = messages; + + //Prefix + botConfig.ModulePrefixes.Clear(); + botConfig.ModulePrefixes.AddRange(new HashSet + { + new ModulePrefix() + { + ModuleName = "Administration", + Prefix = oldConfig.CommandPrefixes.Administration + }, + new ModulePrefix() + { + ModuleName = "Searches", + Prefix = oldConfig.CommandPrefixes.Searches + }, + new ModulePrefix() {ModuleName = "NSFW", Prefix = oldConfig.CommandPrefixes.NSFW}, + new ModulePrefix() + { + ModuleName = "Conversations", + Prefix = oldConfig.CommandPrefixes.Conversations + }, + new ModulePrefix() + { + ModuleName = "ClashOfClans", + Prefix = oldConfig.CommandPrefixes.ClashOfClans + }, + new ModulePrefix() {ModuleName = "Help", Prefix = oldConfig.CommandPrefixes.Help}, + new ModulePrefix() {ModuleName = "Music", Prefix = oldConfig.CommandPrefixes.Music}, + new ModulePrefix() {ModuleName = "Trello", Prefix = oldConfig.CommandPrefixes.Trello}, + new ModulePrefix() {ModuleName = "Games", Prefix = oldConfig.CommandPrefixes.Games}, + new ModulePrefix() + { + ModuleName = "Gambling", + Prefix = oldConfig.CommandPrefixes.Gambling + }, + new ModulePrefix() + { + ModuleName = "Permissions", + Prefix = oldConfig.CommandPrefixes.Permissions + }, + new ModulePrefix() + { + ModuleName = "Programming", + Prefix = oldConfig.CommandPrefixes.Programming + }, + new ModulePrefix() {ModuleName = "Pokemon", Prefix = oldConfig.CommandPrefixes.Pokemon}, + new ModulePrefix() {ModuleName = "Utility", Prefix = oldConfig.CommandPrefixes.Utility} + }); + + //Blacklist + var blacklist = new HashSet(oldConfig.ServerBlacklist.Select(server => new BlacklistItem() { ItemId = server, Type = BlacklistItem.BlacklistType.Server })); + blacklist.AddRange(oldConfig.ChannelBlacklist.Select(channel => new BlacklistItem() { ItemId = channel, Type = BlacklistItem.BlacklistType.Channel })); + blacklist.AddRange(oldConfig.UserBlacklist.Select(user => new BlacklistItem() { ItemId = user, Type = BlacklistItem.BlacklistType.User })); + botConfig.Blacklist = blacklist; + + //Eightball + botConfig.EightBallResponses = new HashSet(oldConfig._8BallResponses.Select(response => new EightBallResponse() { Text = response })); + + //customreactions + uow.CustomReactions.AddRange(oldConfig.CustomReactions.SelectMany(cr => + { + return cr.Value.Select(res => new CustomReaction() + { + GuildId = null, + IsRegex = false, + OwnerOnly = false, + Response = res, + Trigger = cr.Key.ToLowerInvariant(), + }); + }).ToArray()); + + try { File.Move(configPath, "./data/DELETE_ME_config.json"); } catch { } + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/Migration/0_9..cs b/src/NadekoBot/Modules/Administration/Commands/Migration/0_9..cs new file mode 100644 index 00000000..b7de1523 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/Migration/0_9..cs @@ -0,0 +1,195 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace NadekoBot.Modules.Administration.Commands.Migration +{ + public class CommandPrefixes0_9 + { + public string Administration { get; set; } + public string Searches { get; set; } + public string NSFW { get; set; } + public string Conversations { get; set; } + public string ClashOfClans { get; set; } + public string Help { get; set; } + public string Music { get; set; } + public string Trello { get; set; } + public string Games { get; set; } + public string Gambling { get; set; } + public string Permissions { get; set; } + public string Programming { get; set; } + public string Pokemon { get; set; } + public string Utility { get; set; } + } + + public class Config0_9 + { + public bool DontJoinServers { get; set; } + public bool ForwardMessages { get; set; } + public bool ForwardToAllOwners { get; set; } + public bool IsRotatingStatus { get; set; } + public int BufferSize { get; set; } + public List RaceAnimals { get; set; } + public string RemindMessageFormat { get; set; } + public Dictionary> CustomReactions { get; set; } + public List RotatingStatuses { get; set; } + public CommandPrefixes0_9 CommandPrefixes { get; set; } + public List ServerBlacklist { get; set; } + public List ChannelBlacklist { get; set; } + public List UserBlacklist { get; set; } + public List _8BallResponses { get; set; } + public string CurrencySign { get; set; } + public string CurrencyName { get; set; } + public string DMHelpString { get; set; } + public string HelpString { get; set; } + } + + /// + /// Holds a permission list + /// + public class Permissions + { + /// + /// Name of the parent object whose permissions these are + /// + public string Name { get; set; } + /// + /// Module name with allowed/disallowed + /// + public ConcurrentDictionary Modules { get; set; } + /// + /// Command name with allowed/disallowed + /// + public ConcurrentDictionary Commands { get; set; } + /// + /// Should the bot filter invites to other discord servers (and ref links in the future) + /// + public bool FilterInvites { get; set; } + /// + /// Should the bot filter words which are specified in the Words hashset + /// + public bool FilterWords { get; set; } + + public Permissions(string name) + { + Name = name; + Modules = new ConcurrentDictionary(); + Commands = new ConcurrentDictionary(); + FilterInvites = false; + FilterWords = false; + } + + public void CopyFrom(Permissions other) + { + Modules.Clear(); + foreach (var mp in other.Modules) + Modules.AddOrUpdate(mp.Key, mp.Value, (s, b) => mp.Value); + Commands.Clear(); + foreach (var cp in other.Commands) + Commands.AddOrUpdate(cp.Key, cp.Value, (s, b) => cp.Value); + FilterInvites = other.FilterInvites; + FilterWords = other.FilterWords; + } + + public override string ToString() + { + var toReturn = ""; + var bannedModules = Modules.Where(kvp => kvp.Value == false); + var bannedModulesArray = bannedModules as KeyValuePair[] ?? bannedModules.ToArray(); + if (bannedModulesArray.Any()) + { + toReturn += "`Banned Modules:`\n"; + toReturn = bannedModulesArray.Aggregate(toReturn, (current, m) => current + $"\t`[x] {m.Key}`\n"); + } + var bannedCommands = Commands.Where(kvp => kvp.Value == false); + var bannedCommandsArr = bannedCommands as KeyValuePair[] ?? bannedCommands.ToArray(); + if (bannedCommandsArr.Any()) + { + toReturn += "`Banned Commands:`\n"; + toReturn = bannedCommandsArr.Aggregate(toReturn, (current, c) => current + $"\t`[x] {c.Key}`\n"); + } + return toReturn; + } + } + + public class ServerPermissions0_9 + { + /// + /// The guy who can edit the permissions + /// + public string PermissionsControllerRole { get; set; } + /// + /// Does it print the error when a restriction occurs + /// + public bool Verbose { get; set; } + /// + /// The id of the thing (user/server/channel) + /// + public ulong Id { get; set; } //a string because of the role name. + /// + /// Permission object bound to the id of something/role name + /// + public Permissions Permissions { get; set; } + /// + /// Banned words, usually profanities, like word "java" + /// + public HashSet Words { get; set; } + + public Dictionary UserPermissions { get; set; } + public Dictionary ChannelPermissions { get; set; } + public Dictionary RolePermissions { get; set; } + /// + /// Dictionary of command names with their respective cooldowns + /// + public ConcurrentDictionary CommandCooldowns { get; set; } + + public ServerPermissions0_9(ulong id, string name) + { + Id = id; + PermissionsControllerRole = "Nadeko"; + Verbose = true; + + Permissions = new Permissions(name); + Permissions.Modules.TryAdd("NSFW", false); + UserPermissions = new Dictionary(); + ChannelPermissions = new Dictionary(); + RolePermissions = new Dictionary(); + CommandCooldowns = new ConcurrentDictionary(); + Words = new HashSet(); + } + } + + public class ServerSpecificConfig + { + public bool VoicePlusTextEnabled { get; set; } + public bool SendPrivateMessageOnMention { get; set; } + public ulong? LogChannel { get; set; } = null; + public ulong? LogPresenceChannel { get; set; } = null; + public HashSet LogserverIgnoreChannels { get; set; } + public ConcurrentDictionary VoiceChannelLog { get; set; } + public HashSet ListOfSelfAssignableRoles { get; set; } + public ulong AutoAssignedRole { get; set; } + public ConcurrentDictionary GenerateCurrencyChannels { get; set; } + public bool AutoDeleteMessagesOnCommand { get; set; } + public bool ExclusiveSelfAssignedRoles { get; set; } + public float DefaultMusicVolume { get; set; } + public HashSet ObservingStreams { get; set; } + } + + public class StreamNotificationConfig0_9 + { + public string Username { get; set; } + public StreamType Type { get; set; } + public ulong ServerId { get; set; } + public ulong ChannelId { get; set; } + public bool LastStatus { get; set; } + + public enum StreamType + { + Twitch, + Beam, + Hitbox, + YoutubeGaming + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Administration/Commands/Migration/MigrationException.cs b/src/NadekoBot/Modules/Administration/Commands/Migration/MigrationException.cs new file mode 100644 index 00000000..307ed63d --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/Migration/MigrationException.cs @@ -0,0 +1,9 @@ +using System; + +namespace NadekoBot.Modules.Administration.Commands.Migration +{ + public class MigrationException : Exception + { + + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs new file mode 100644 index 00000000..3e047508 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/PlayingRotateCommands.cs @@ -0,0 +1,173 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class PlayingRotateCommands + { + private Logger _log { get; } + public static List RotatingStatusMessages { get; } + public static bool RotatingStatuses { get; private set; } = false; + + static PlayingRotateCommands() + { + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.BotConfig.GetOrCreate(); + RotatingStatusMessages = conf.RotatingStatusMessages; + RotatingStatuses = conf.RotatingStatuses; + } + } + + public PlayingRotateCommands() + { + _log = LogManager.GetCurrentClassLogger(); + Task.Run(async () => + { + var index = 0; + do + { + try + { + if (!RotatingStatuses) + continue; + else + { + if (index >= RotatingStatusMessages.Count) + index = 0; + + if (!RotatingStatusMessages.Any()) + continue; + var status = RotatingStatusMessages[index++].Status; + if (string.IsNullOrWhiteSpace(status)) + continue; + PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value())); + await NadekoBot.Client.SetGame(status); + } + } + catch (Exception ex) + { + _log.Warn("Rotating playing status errored.\n" + ex); + } + finally + { + await Task.Delay(TimeSpan.FromMinutes(1)); + } + } while (true); + }); + } + + public static Dictionary> PlayingPlaceholders { get; } = + new Dictionary> { + {"%servers%", () => NadekoBot.Client.GetGuilds().Count().ToString()}, + {"%users%", () => NadekoBot.Client.GetGuilds().Select(s => s.GetUsers().Count).Sum().ToString()}, + {"%playing%", () => { + var cnt = Music.Music.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null); + if (cnt != 1) return cnt.ToString(); + try { + var mp = Music.Music.MusicPlayers.FirstOrDefault(); + return mp.Value.CurrentSong.SongInfo.Title; + } + catch { + return "No songs"; + } + } + }, + {"%queued%", () => Music.Music.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()} + }; + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task RotatePlaying(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.BotConfig.GetOrCreate(); + + RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses; + await uow.CompleteAsync(); + } + if (RotatingStatuses) + await channel.SendMessageAsync("🆗 **Rotating playing status enabled.**"); + else + await channel.SendMessageAsync("ℹ️ **Rotating playing status disabled.**"); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task AddPlaying(IUserMessage umsg, [Remainder] string status) + { + var channel = (ITextChannel)umsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.BotConfig.GetOrCreate(); + var toAdd = new PlayingStatus { Status = status }; + config.RotatingStatusMessages.Add(toAdd); + RotatingStatusMessages.Add(toAdd); + await uow.CompleteAsync(); + } + + await channel.SendMessageAsync("✅ **Added.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task ListPlaying(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + + if (!RotatingStatusMessages.Any()) + await channel.SendMessageAsync("❎ **No rotating playing statuses set.**"); + else + { + var i = 1; + await channel.SendMessageAsync($"ℹ️ {umsg.Author.Mention} `Here is a list of rotating statuses:`\n\n\t" + string.Join("\n\t", RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}"))); + } + + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task RemovePlaying(IUserMessage umsg, int index) + { + var channel = (ITextChannel)umsg.Channel; + index -= 1; + + string msg = ""; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.BotConfig.GetOrCreate(); + + if (index >= config.RotatingStatusMessages.Count) + return; + msg = config.RotatingStatusMessages[index].Status; + config.RotatingStatusMessages.RemoveAt(index); + RotatingStatusMessages.RemoveAt(index); + await uow.CompleteAsync(); + } + await channel.SendMessageAsync($"🗑 **Removed the the playing message:** {msg}").ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs b/src/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs new file mode 100644 index 00000000..224461b5 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs @@ -0,0 +1,133 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class RatelimitCommand + { + public static ConcurrentDictionary RatelimitingChannels = new ConcurrentDictionary(); + private Logger _log { get; } + + private ShardedDiscordClient _client { get; } + + public class Ratelimiter + { + public class RatelimitedUser + { + public ulong UserId { get; set; } + public int MessageCount { get; set; } = 0; + } + + public ulong ChannelId { get; set; } + + public int MaxMessages { get; set; } + public int PerSeconds { get; set; } + + public CancellationTokenSource cancelSource { get; set; } = new CancellationTokenSource(); + + public ConcurrentDictionary Users { get; set; } = new ConcurrentDictionary(); + + public bool CheckUserRatelimit(ulong id) + { + RatelimitedUser usr = Users.GetOrAdd(id, (key) => new RatelimitedUser() { UserId = id }); + if (usr.MessageCount == MaxMessages) + { + return true; + } + else + { + usr.MessageCount++; + var t = Task.Run(async () => { + try + { + await Task.Delay(PerSeconds * 1000, cancelSource.Token); + } + catch (OperationCanceledException) { } + usr.MessageCount--; + }); + return false; + } + + } + } + + public RatelimitCommand() + { + this._client = NadekoBot.Client; + this._log = LogManager.GetCurrentClassLogger(); + + _client.MessageReceived += (umsg) => + { + var t = Task.Run(async () => + { + var usrMsg = umsg as IUserMessage; + var channel = usrMsg.Channel as ITextChannel; + + if (channel == null || usrMsg.IsAuthor()) + return; + Ratelimiter limiter; + if (!RatelimitingChannels.TryGetValue(channel.Id, out limiter)) + return; + + if (limiter.CheckUserRatelimit(usrMsg.Author.Id)) + try { await usrMsg.DeleteAsync(); } catch (Exception ex) { _log.Warn(ex); } + }); + return Task.CompletedTask; + }; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Slowmode(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + Ratelimiter throwaway; + if (RatelimitingChannels.TryRemove(channel.Id, out throwaway)) + { + throwaway.cancelSource.Cancel(); + await channel.SendMessageAsync("ℹ️ **Slow mode disabled.**").ConfigureAwait(false); + return; + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Slowmode(IUserMessage umsg, int msg, int perSec) + { + await Slowmode(umsg).ConfigureAwait(false); // disable if exists + var channel = (ITextChannel)umsg.Channel; + + if (msg < 1 || perSec < 1 || msg > 100 || perSec > 3600) + { + await channel.SendMessageAsync("⚠️ `Invalid parameters.`"); + return; + } + var toAdd = new Ratelimiter() + { + ChannelId = channel.Id, + MaxMessages = msg, + PerSeconds = perSec, + }; + if(RatelimitingChannels.TryAdd(channel.Id, toAdd)) + { + await channel.SendMessageAsync("✅ **Slow mode initiated: " + + $"Users can't send more than `{toAdd.MaxMessages} message(s)` every `{toAdd.PerSeconds} second(s)`.**") + .ConfigureAwait(false); + } + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs new file mode 100644 index 00000000..1a8bb600 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs @@ -0,0 +1,251 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class SelfAssignedRolesCommands + { + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task AdSarm(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + bool newval; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + newval = config.AutoDeleteSelfAssignedRoleMessages = !config.AutoDeleteSelfAssignedRoleMessages; + await uow.CompleteAsync().ConfigureAwait(false); + } + + await channel.SendMessageAsync($"ℹ️ Automatic deleting of `iam` and `iamn` confirmations has been {(newval ? "**enabled**" : "**disabled**")}.") + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task Asar(IUserMessage umsg, [Remainder] IRole role) + { + var channel = (ITextChannel)umsg.Channel; + + IEnumerable roles; + + string msg; + using (var uow = DbHandler.UnitOfWork()) + { + roles = uow.SelfAssignedRoles.GetFromGuild(channel.Guild.Id); + if (roles.Any(s => s.RoleId == role.Id && s.GuildId == role.GuildId)) + { + msg = $"💢 Role **{role.Name}** is already in the list."; + } + else + { + uow.SelfAssignedRoles.Add(new SelfAssignedRole { + RoleId = role.Id, + GuildId = role.GuildId + }); + await uow.CompleteAsync(); + msg = $"🆗 Role **{role.Name}** added to the list."; + } + } + await channel.SendMessageAsync(msg.ToString()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task Rsar(IUserMessage umsg, [Remainder] IRole role) + { + var channel = (ITextChannel)umsg.Channel; + + bool success; + using (var uow = DbHandler.UnitOfWork()) + { + success = uow.SelfAssignedRoles.DeleteByGuildAndRoleId(role.GuildId, role.Id); + await uow.CompleteAsync(); + } + if (!success) + { + await channel.SendMessageAsync("❎ That role is not self-assignable.").ConfigureAwait(false); + return; + } + await channel.SendMessageAsync($"🗑 **{role.Name}** has been removed from the list of self-assignable roles.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Lsar(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + var toRemove = new ConcurrentHashSet(); + var removeMsg = new StringBuilder(); + var msg = new StringBuilder(); + using (var uow = DbHandler.UnitOfWork()) + { + var roleModels = uow.SelfAssignedRoles.GetFromGuild(channel.Guild.Id); + msg.AppendLine($"ℹ️ There are `{roleModels.Count()}` self assignable roles:"); + + foreach (var roleModel in roleModels) + { + var role = channel.Guild.Roles.FirstOrDefault(r => r.Id == roleModel.RoleId); + if (role == null) + { + uow.SelfAssignedRoles.Remove(roleModel); + } + else + { + msg.Append($"**{role.Name}**, "); + } + } + foreach (var role in toRemove) + { + removeMsg.AppendLine($"`{role.RoleId} not found. Cleaned up.`"); + } + await uow.CompleteAsync(); + } + await channel.SendMessageAsync(msg.ToString() + "\n\n" + removeMsg.ToString()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task Tesar(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + bool areExclusive; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + + areExclusive = config.ExclusiveSelfAssignedRoles = !config.ExclusiveSelfAssignedRoles; + await uow.CompleteAsync(); + } + string exl = areExclusive ? "**exclusive**." : "**not exclusive**."; + await channel.SendMessageAsync("ℹ️ Self assigned roles are now " + exl); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Iam(IUserMessage umsg, [Remainder] IRole role) + { + var channel = (ITextChannel)umsg.Channel; + var guildUser = (IGuildUser)umsg.Author; + var usrMsg = (IUserMessage)umsg; + + GuildConfig conf; + IEnumerable roles; + using (var uow = DbHandler.UnitOfWork()) + { + conf = uow.GuildConfigs.For(channel.Guild.Id); + roles = uow.SelfAssignedRoles.GetFromGuild(channel.Guild.Id); + } + SelfAssignedRole roleModel; + if ((roleModel = roles.FirstOrDefault(r=>r.RoleId == role.Id)) == null) + { + await channel.SendMessageAsync("💢 That role is not self-assignable.").ConfigureAwait(false); + return; + } + if (guildUser.Roles.Contains(role)) + { + await channel.SendMessageAsync($"❎ You already have **{role.Name}** role.").ConfigureAwait(false); + return; + } + + if (conf.ExclusiveSelfAssignedRoles) + { + var sameRoles = guildUser.Roles.Where(r => roles.Any(rm => rm.RoleId == r.Id)); + if (sameRoles.Any()) + { + await channel.SendMessageAsync($"❎ You already have **{sameRoles.FirstOrDefault().Name}** `exclusive self-assigned` role.").ConfigureAwait(false); + return; + } + } + try + { + await guildUser.AddRolesAsync(role).ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync($"⚠️ I am unable to add that role to you. `I can't add roles to owners or other roles higher than my role in the role hierarchy.`").ConfigureAwait(false); + Console.WriteLine(ex); + return; + } + var msg = await channel.SendMessageAsync($"🆗 You now have **{role.Name}** role.").ConfigureAwait(false); + + if (conf.AutoDeleteSelfAssignedRoleMessages) + { + var t = Task.Run(async () => + { + await Task.Delay(3000).ConfigureAwait(false); + try { await msg.DeleteAsync().ConfigureAwait(false); } catch { } // if 502 or something, i don't want bot crashing + try { await usrMsg.DeleteAsync().ConfigureAwait(false); } catch { } + }); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Iamnot(IUserMessage umsg, [Remainder] IRole role) + { + var channel = (ITextChannel)umsg.Channel; + var guildUser = (IGuildUser)umsg.Author; + + GuildConfig conf; + IEnumerable roles; + using (var uow = DbHandler.UnitOfWork()) + { + conf = uow.GuildConfigs.For(channel.Guild.Id); + roles = uow.SelfAssignedRoles.GetFromGuild(channel.Guild.Id); + } + SelfAssignedRole roleModel; + if ((roleModel = roles.FirstOrDefault(r => r.RoleId == role.Id)) == null) + { + await channel.SendMessageAsync("💢 That role is not self-assignable.").ConfigureAwait(false); + return; + } + if (!guildUser.Roles.Contains(role)) + { + await channel.SendMessageAsync($"❎ You don't have **{role.Name}** role.").ConfigureAwait(false); + return; + } + try + { + await guildUser.RemoveRolesAsync(role).ConfigureAwait(false); + } + catch (Exception) + { + await channel.SendMessageAsync($"⚠️ I am unable to add that role to you. `I can't remove roles to owners or other roles higher than my role in the role hierarchy.`").ConfigureAwait(false); + return; + } + var msg = await channel.SendMessageAsync($"🆗 You no longer have **{role.Name}** role.").ConfigureAwait(false); + + if (conf.AutoDeleteSelfAssignedRoleMessages) + { + var t = Task.Run(async () => + { + await Task.Delay(3000).ConfigureAwait(false); + try { await msg.DeleteAsync().ConfigureAwait(false); } catch { } // if 502 or something, i don't want bot crashing + try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { } + }); + } + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs b/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs new file mode 100644 index 00000000..0c634461 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/SelfCommands.cs @@ -0,0 +1,50 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + class SelfCommands + { + private ShardedDiscordClient _client; + + public SelfCommands(ShardedDiscordClient client) + { + this._client = client; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Leave(IUserMessage umsg, [Remainder] string guildStr) + { + var channel = (ITextChannel)umsg.Channel; + + guildStr = guildStr.Trim().ToUpperInvariant(); + var server = _client.GetGuilds().FirstOrDefault(g => g.Id.ToString().Trim().ToUpperInvariant() == guildStr) ?? + _client.GetGuilds().FirstOrDefault(g => g.Name.Trim().ToUpperInvariant() == guildStr); + + if (server == null) + { + await channel.SendMessageAsync("⚠️ Cannot find that server").ConfigureAwait(false); + return; + } + if (server.OwnerId != _client.GetCurrentUser().Id) + { + await server.LeaveAsync().ConfigureAwait(false); + await channel.SendMessageAsync("✅ Left server " + server.Name).ConfigureAwait(false); + } + else + { + await server.DeleteAsync().ConfigureAwait(false); + await channel.SendMessageAsync("Deleted server " + server.Name).ConfigureAwait(false); + } + } + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs b/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs new file mode 100644 index 00000000..3afcb46c --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/ServerGreetCommands.cs @@ -0,0 +1,409 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database; +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class ServerGreetCommands + { + public static long Greeted = 0; + private Logger _log; + + public ServerGreetCommands() + { + NadekoBot.Client.UserJoined += UserJoined; + NadekoBot.Client.UserLeft += UserLeft; + _log = LogManager.GetCurrentClassLogger(); + } + + private Task UserLeft(IGuildUser user) + { + var leftTask = Task.Run(async () => + { + try + { + GuildConfig conf; + using (var uow = DbHandler.UnitOfWork()) + { + conf = uow.GuildConfigs.For(user.Guild.Id); + } + + if (!conf.SendChannelByeMessage) return; + var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.ByeMessageChannelId); + + if (channel == null) //maybe warn the server owner that the channel is missing + return; + + var msg = conf.ChannelByeMessageText.Replace("%user%", user.Username).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); + if (string.IsNullOrWhiteSpace(msg)) + return; + try + { + var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false); + if (conf.AutoDeleteByeMessagesTimer > 0) + { + var t = Task.Run(async () => + { + await Task.Delay(conf.AutoDeleteByeMessagesTimer * 1000).ConfigureAwait(false); // 5 minutes + try { await toDelete.DeleteAsync().ConfigureAwait(false); } catch { } + }); + } + } + catch (Exception ex) { _log.Warn(ex); } + } + catch { } + }); + return Task.CompletedTask; + } + + private Task UserJoined(IGuildUser user) + { + var joinedTask = Task.Run(async () => + { + try + { + GuildConfig conf; + using (var uow = DbHandler.UnitOfWork()) + { + conf = uow.GuildConfigs.For(user.Guild.Id); + } + + if (conf.SendChannelGreetMessage) + { + var channel = (await user.Guild.GetTextChannelsAsync()).SingleOrDefault(c => c.Id == conf.GreetMessageChannelId); + if (channel != null) //maybe warn the server owner that the channel is missing + { + var msg = conf.ChannelGreetMessageText.Replace("%user%", user.Mention).Replace("%id%", user.Id.ToString()).Replace("%server%", user.Guild.Name); + if (!string.IsNullOrWhiteSpace(msg)) + { + try + { + var toDelete = await channel.SendMessageAsync(msg.SanitizeMentions()).ConfigureAwait(false); + if (conf.AutoDeleteGreetMessagesTimer > 0) + { + var t = Task.Run(async () => + { + await Task.Delay(conf.AutoDeleteGreetMessagesTimer * 1000).ConfigureAwait(false); // 5 minutes + try { await toDelete.DeleteAsync().ConfigureAwait(false); } catch { } + }); + } + } + catch (Exception ex) { _log.Warn(ex); } + } + } + } + + if (conf.SendDmGreetMessage) + { + var channel = await user.CreateDMChannelAsync(); + + if (channel != null) + { + var msg = conf.DmGreetMessageText.Replace("%user%", user.Username).Replace("%server%", user.Guild.Name); + if (!string.IsNullOrWhiteSpace(msg)) + { + await channel.SendMessageAsync(msg).ConfigureAwait(false); + } + } + } + } + catch { } + }); + return Task.CompletedTask; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task GreetDel(IUserMessage umsg, int timer = 30) + { + var channel = (ITextChannel)umsg.Channel; + if (timer < 0 || timer > 600) + return; + + await ServerGreetCommands.SetGreetDel(channel.Guild.Id, timer).ConfigureAwait(false); + + if (timer > 0) + await channel.SendMessageAsync($"🆗 Greet messages **will be deleted** after `{timer} seconds`.").ConfigureAwait(false); + else + await channel.SendMessageAsync("ℹ️ Automatic deletion of greet messages has been **disabled**.").ConfigureAwait(false); + } + + private static async Task SetGreetDel(ulong id, int timer) + { + if (timer < 0 || timer > 600) + return; + + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(id); + conf.AutoDeleteGreetMessagesTimer = timer; + uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task Greet(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + var enabled = await ServerGreetCommands.SetGreet(channel.Guild.Id, channel.Id).ConfigureAwait(false); + + if (enabled) + await channel.SendMessageAsync("✅ Greeting messages **enabled** on this channel.").ConfigureAwait(false); + else + await channel.SendMessageAsync("ℹ️ Greeting messages **disabled**.").ConfigureAwait(false); + } + + private static async Task SetGreet(ulong guildId, ulong channelId, bool? value = null) + { + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(guildId); + enabled = conf.SendChannelGreetMessage = value ?? !conf.SendChannelGreetMessage; + conf.GreetMessageChannelId = channelId; + uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); + } + return enabled; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task GreetMsg(IUserMessage umsg, [Remainder] string text = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(text)) + { + GuildConfig config; + using (var uow = DbHandler.UnitOfWork()) + { + config = uow.GuildConfigs.For(channel.Guild.Id); + } + await channel.SendMessageAsync("ℹ️ Current **greet** message: `" + config.ChannelGreetMessageText?.SanitizeMentions() + "`"); + return; + } + + var sendGreetEnabled = ServerGreetCommands.SetGreetMessage(channel.Guild.Id, ref text); + + await channel.SendMessageAsync("🆗 New greet message **set**.").ConfigureAwait(false); + if (!sendGreetEnabled) + await channel.SendMessageAsync("ℹ️ Enable greet messsages by typing `.greet`").ConfigureAwait(false); + } + + public static bool SetGreetMessage(ulong guildId, ref string message) + { + message = message?.SanitizeMentions(); + + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentNullException(nameof(message)); + + bool greetMsgEnabled; + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(guildId); + conf.ChannelGreetMessageText = message; + greetMsgEnabled = conf.SendChannelGreetMessage; + + uow.GuildConfigs.Update(conf); + uow.Complete(); + } + return greetMsgEnabled; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task GreetDm(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + var enabled = await ServerGreetCommands.SetGreetDm(channel.Guild.Id).ConfigureAwait(false); + + if (enabled) + await channel.SendMessageAsync("🆗 DM Greet announcements **enabled**.").ConfigureAwait(false); + else + await channel.SendMessageAsync("ℹ️ Greet announcements **disabled**.").ConfigureAwait(false); + } + + private static async Task SetGreetDm(ulong guildId, bool? value = null) + { + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(guildId); + enabled = conf.SendDmGreetMessage = value ?? !conf.SendDmGreetMessage; + uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); + } + return enabled; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task GreetDmMsg(IUserMessage umsg, [Remainder] string text = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(text)) + { + GuildConfig config; + using (var uow = DbHandler.UnitOfWork()) + { + config = uow.GuildConfigs.For(channel.Guild.Id); + } + await channel.SendMessageAsync("ℹ️ Current **DM greet** message: `" + config.DmGreetMessageText?.SanitizeMentions() + "`"); + return; + } + + var sendGreetEnabled = ServerGreetCommands.SetGreetDmMessage(channel.Guild.Id, ref text); + + await channel.SendMessageAsync("🆗 New DM greet message **set**.").ConfigureAwait(false); + if (!sendGreetEnabled) + await channel.SendMessageAsync($"ℹ️ Enable DM greet messsages by typing `{NadekoBot.ModulePrefixes[typeof(Administration).Name]}greetdm`").ConfigureAwait(false); + } + + public static bool SetGreetDmMessage(ulong guildId, ref string message) + { + message = message?.SanitizeMentions(); + + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentNullException(nameof(message)); + + bool greetMsgEnabled; + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(guildId); + conf.DmGreetMessageText = message; + greetMsgEnabled = conf.SendDmGreetMessage; + + uow.GuildConfigs.Update(conf); + uow.Complete(); + } + return greetMsgEnabled; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task Bye(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + var enabled = await ServerGreetCommands.SetBye(channel.Guild.Id, channel.Id).ConfigureAwait(false); + + if (enabled) + await channel.SendMessageAsync("✅ Bye announcements **enabled** on this channel.").ConfigureAwait(false); + else + await channel.SendMessageAsync("ℹ️ Bye announcements **disabled**.").ConfigureAwait(false); + } + + private static async Task SetBye(ulong guildId, ulong channelId, bool? value = null) + { + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(guildId); + enabled = conf.SendChannelByeMessage = value ?? !conf.SendChannelByeMessage; + conf.ByeMessageChannelId = channelId; + uow.GuildConfigs.Update(conf); + await uow.CompleteAsync(); + } + return enabled; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task ByeMsg(IUserMessage umsg, [Remainder] string text = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(text)) + { + GuildConfig config; + using (var uow = DbHandler.UnitOfWork()) + { + config = uow.GuildConfigs.For(channel.Guild.Id); + } + await channel.SendMessageAsync("ℹ️ Current **bye** message: `" + config.ChannelByeMessageText?.SanitizeMentions() + "`"); + return; + } + + var sendByeEnabled = ServerGreetCommands.SetByeMessage(channel.Guild.Id, ref text); + + await channel.SendMessageAsync("🆗 New bye message **set**.").ConfigureAwait(false); + if (!sendByeEnabled) + await channel.SendMessageAsync($"ℹ️ Enable bye messsages by typing `{NadekoBot.ModulePrefixes[typeof(Administration).Name]}bye`").ConfigureAwait(false); + } + + public static bool SetByeMessage(ulong guildId, ref string message) + { + message = message?.SanitizeMentions(); + + if (string.IsNullOrWhiteSpace(message)) + throw new ArgumentNullException(nameof(message)); + + bool byeMsgEnabled; + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(guildId); + conf.ChannelByeMessageText = message; + byeMsgEnabled = conf.SendChannelByeMessage; + + uow.GuildConfigs.Update(conf); + uow.Complete(); + } + return byeMsgEnabled; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageGuild)] + public async Task ByeDel(IUserMessage umsg, int timer = 30) + { + var channel = (ITextChannel)umsg.Channel; + + await ServerGreetCommands.SetByeDel(channel.Guild.Id, timer).ConfigureAwait(false); + + if (timer > 0) + await channel.SendMessageAsync($"🆗 Bye messages **will be deleted** after `{timer} seconds`.").ConfigureAwait(false); + else + await channel.SendMessageAsync("ℹ️ Automatic deletion of bye messages has been **disabled**.").ConfigureAwait(false); + } + + private static async Task SetByeDel(ulong id, int timer) + { + if (timer < 0 || timer > 600) + return; + + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(id); + conf.AutoDeleteByeMessagesTimer = timer; + uow.GuildConfigs.Update(conf); + await uow.CompleteAsync().ConfigureAwait(false); + } + } + + } + } +} diff --git a/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs b/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs new file mode 100644 index 00000000..96906532 --- /dev/null +++ b/src/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommands.cs @@ -0,0 +1,190 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Administration +{ + public partial class Administration + { + [Group] + public class VoicePlusTextCommands + { + Regex channelNameRegex = new Regex(@"[^a-zA-Z0-9 -]", RegexOptions.Compiled); + + private ConcurrentHashSet voicePlusTextCache; + public VoicePlusTextCommands() + { + using (var uow = DbHandler.UnitOfWork()) + { + voicePlusTextCache = new ConcurrentHashSet(NadekoBot.AllGuildConfigs.Where(g => g.VoicePlusTextEnabled).Select(g => g.GuildId)); + } + NadekoBot.Client.UserVoiceStateUpdated += UserUpdatedEventHandler; + } + + private Task UserUpdatedEventHandler(IUser iuser, IVoiceState before, IVoiceState after) + { + var user = (iuser as IGuildUser); + var guild = user?.Guild; + + if (guild == null) + return Task.CompletedTask; + var task = Task.Run(async () => + { + try + { + var botUserPerms = guild.GetCurrentUser().GuildPermissions; + + if (before.VoiceChannel == after.VoiceChannel) return; + + if (!voicePlusTextCache.Contains(guild.Id)) + return; + + if (!botUserPerms.ManageChannels || !botUserPerms.ManageRoles) + { + try + { + await (await guild.GetOwnerAsync()).SendMessageAsync( + "⚠️ I don't have **manage server** and/or **manage channels** permission," + + $" so I cannot run `voice+text` on **{guild.Name}** server.").ConfigureAwait(false); + } + catch { } + using (var uow = DbHandler.UnitOfWork()) + { + uow.GuildConfigs.For(guild.Id).VoicePlusTextEnabled = false; + voicePlusTextCache.TryRemove(guild.Id); + await uow.CompleteAsync().ConfigureAwait(false); + } + return; + } + + + var beforeVch = before.VoiceChannel; + if (beforeVch != null) + { + var textChannel = guild.GetTextChannels().Where(t => t.Name == GetChannelName(beforeVch.Name).ToLowerInvariant()).FirstOrDefault(); + if (textChannel != null) + await textChannel.AddPermissionOverwriteAsync(user, + new OverwritePermissions(readMessages: PermValue.Deny, + sendMessages: PermValue.Deny)).ConfigureAwait(false); + } + var afterVch = after.VoiceChannel; + if (afterVch != null && guild.AFKChannelId != afterVch.Id) + { + var textChannel = guild.GetTextChannels() + .Where(t => t.Name == GetChannelName(afterVch.Name).ToLowerInvariant()) + .FirstOrDefault(); + if (textChannel == null) + { + textChannel = (await guild.CreateTextChannelAsync(GetChannelName(afterVch.Name).ToLowerInvariant()).ConfigureAwait(false)); + await textChannel.AddPermissionOverwriteAsync(guild.EveryoneRole, + new OverwritePermissions(readMessages: PermValue.Deny, + sendMessages: PermValue.Deny)).ConfigureAwait(false); + } + await textChannel.AddPermissionOverwriteAsync(user, + new OverwritePermissions(readMessages: PermValue.Allow, + sendMessages: PermValue.Allow)).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + }); + return Task.CompletedTask; + } + + private string GetChannelName(string voiceName) => + channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice"; + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageRoles)] + [RequirePermission(GuildPermission.ManageChannels)] + public async Task VoicePlusText(IUserMessage msg) + { + var channel = (ITextChannel)msg.Channel; + var guild = channel.Guild; + + var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false); + if (!botUser.GuildPermissions.ManageRoles || !botUser.GuildPermissions.ManageChannels) + { + await channel.SendMessageAsync("💢 I require atleast **manage roles** and **manage channels permissions** to enable this feature. `(preffered Administration permission)`"); + return; + } + + if (!botUser.GuildPermissions.Administrator) + { + try + { + await channel.SendMessageAsync("⚠️ You are enabling this feature and **I do not have ADMINISTRATOR permissions**. " + + "`This may cause some issues, and you will have to clean up text channels yourself afterwards.`"); + } + catch { } + } + try + { + bool isEnabled; + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.GuildConfigs.For(guild.Id); + isEnabled = conf.VoicePlusTextEnabled = !conf.VoicePlusTextEnabled; + await uow.CompleteAsync().ConfigureAwait(false); + } + if (!isEnabled) + { + voicePlusTextCache.TryRemove(guild.Id); + foreach (var textChannel in guild.GetTextChannels().Where(c => c.Name.EndsWith("-voice"))) + { + try { await textChannel.DeleteAsync().ConfigureAwait(false); } catch { } + } + await channel.SendMessageAsync("ℹ️ Successfuly **removed** voice + text feature.").ConfigureAwait(false); + return; + } + voicePlusTextCache.Add(guild.Id); + await channel.SendMessageAsync("🆗 Successfuly **enabled** voice + text feature.").ConfigureAwait(false); + + } + catch (Exception ex) + { + await channel.SendMessageAsync(ex.ToString()).ConfigureAwait(false); + } + } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageChannels)] + [RequirePermission(GuildPermission.ManageRoles)] + public async Task CleanVPlusT(IUserMessage msg) + { + var channel = (ITextChannel)msg.Channel; + var guild = channel.Guild; + var botUser = await guild.GetCurrentUserAsync().ConfigureAwait(false); + if (!botUser.GuildPermissions.Administrator) + { + await channel.SendMessageAsync("⚠️ I need **Administrator permission** to do that.").ConfigureAwait(false); + return; + } + + var allTxtChannels = guild.GetTextChannels().Where(c => c.Name.EndsWith("-voice")); + var validTxtChannelNames = guild.GetVoiceChannels().Select(c => GetChannelName(c.Name).ToLowerInvariant()); + + var invalidTxtChannels = allTxtChannels.Where(c => !validTxtChannelNames.Contains(c.Name)); + + foreach (var c in invalidTxtChannels) + { + try { await c.DeleteAsync().ConfigureAwait(false); } catch { } + await Task.Delay(500); + } + + await channel.SendMessageAsync("✅ Done.").ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs new file mode 100644 index 00000000..c7cfdaad --- /dev/null +++ b/src/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs @@ -0,0 +1,361 @@ +using Discord.Commands; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Discord; +using NadekoBot.Services; +using NadekoBot.Attributes; +using Discord.WebSocket; +using NadekoBot.Services.Database.Models; +using System.Linq; + +namespace NadekoBot.Modules.ClashOfClans +{ + [NadekoModule("ClashOfClans", ",")] + public class ClashOfClans : DiscordModule + { + public static ConcurrentDictionary> ClashWars { get; set; } = new ConcurrentDictionary>(); + + static ClashOfClans() + { + using (var uow = DbHandler.UnitOfWork()) + { + ClashWars = new ConcurrentDictionary>( + uow.ClashOfClans + .GetAllWars() + .Select(cw => + { + cw.Channel = NadekoBot.Client.GetGuild(cw.GuildId) + ?.GetTextChannel(cw.ChannelId); + return cw; + }) + .Where(cw => cw?.Channel != null) + .GroupBy(cw => cw.GuildId) + .ToDictionary(g => g.Key, g => g.ToList())); + } + } + public ClashOfClans(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + } + + private static async Task CheckWar(TimeSpan callExpire, ClashWar war) + { + var Bases = war.Bases; + for (var i = 0; i < Bases.Count; i++) + { + if (Bases[i].CallUser == null) continue; + if (!Bases[i].BaseDestroyed && DateTime.UtcNow - Bases[i].TimeAdded >= callExpire) + { + Bases[i] = null; + try { await war.Channel.SendMessageAsync($"❗🔰**Claim from @{Bases[i].CallUser} for a war against {war.ShortPrint()} has expired.**").ConfigureAwait(false); } catch { } + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task CreateWar(IUserMessage umsg, int size, [Remainder] string enemyClan = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (!(umsg.Author as IGuildUser).GuildPermissions.ManageChannels) + return; + + if (string.IsNullOrWhiteSpace(enemyClan)) + return; + + if (size < 10 || size > 50 || size % 5 != 0) + { + await channel.SendMessageAsync("💢🔰 Not a Valid war size").ConfigureAwait(false); + return; + } + List wars; + if (!ClashWars.TryGetValue(channel.Guild.Id, out wars)) + { + wars = new List(); + if (!ClashWars.TryAdd(channel.Guild.Id, wars)) + return; + } + + + var cw = await CreateWar(enemyClan, size, channel.Guild.Id, umsg.Channel.Id); + + wars.Add(cw); + await channel.SendMessageAsync($"❗🔰**CREATED CLAN WAR AGAINST {cw.ShortPrint()}**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task StartWar(IUserMessage umsg, [Remainder] string number = null) + { + var channel = (ITextChannel)umsg.Channel; + + int num = 0; + int.TryParse(number, out num); + + var warsInfo = GetWarInfo(umsg, num); + if (warsInfo == null) + { + await channel.SendMessageAsync("💢🔰 **That war does not exist.**").ConfigureAwait(false); + return; + } + var war = warsInfo.Item1[warsInfo.Item2]; + try + { + war.Start(); + await channel.SendMessageAsync($"🔰**STARTED WAR AGAINST {war.ShortPrint()}**").ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync($"🔰**WAR AGAINST {war.ShortPrint()} HAS ALREADY STARTED**").ConfigureAwait(false); + } + SaveWar(war); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ListWar(IUserMessage umsg, [Remainder] string number = null) + { + var channel = (ITextChannel)umsg.Channel; + + // if number is null, print all wars in a short way + if (string.IsNullOrWhiteSpace(number)) + { + //check if there are any wars + List wars = null; + ClashWars.TryGetValue(channel.Guild.Id, out wars); + if (wars == null || wars.Count == 0) + { + await channel.SendMessageAsync("🔰 **No active wars.**").ConfigureAwait(false); + return; + } + + var sb = new StringBuilder(); + sb.AppendLine("🔰 **LIST OF ACTIVE WARS**"); + sb.AppendLine("**-------------------------**"); + for (var i = 0; i < wars.Count; i++) + { + sb.AppendLine($"**#{i + 1}.** `Enemy:` **{wars[i].EnemyClan}**"); + sb.AppendLine($"\t\t`Size:` **{wars[i].Size} v {wars[i].Size}**"); + sb.AppendLine("**-------------------------**"); + } + await channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false); + return; + + } + var num = 0; + int.TryParse(number, out num); + //if number is not null, print the war needed + var warsInfo = GetWarInfo(umsg, num); + if (warsInfo == null) + { + await channel.SendMessageAsync("💢🔰 **That war does not exist.**").ConfigureAwait(false); + return; + } + await channel.SendMessageAsync(warsInfo.Item1[warsInfo.Item2].ToPrettyString()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Claim(IUserMessage umsg, int number, int baseNumber, [Remainder] string other_name = null) + { + var channel = (ITextChannel)umsg.Channel; + var warsInfo = GetWarInfo(umsg, number); + if (warsInfo == null || warsInfo.Item1.Count == 0) + { + await channel.SendMessageAsync("💢🔰 **That war does not exist.**").ConfigureAwait(false); + return; + } + var usr = + string.IsNullOrWhiteSpace(other_name) ? + umsg.Author.Username : + other_name; + try + { + var war = warsInfo.Item1[warsInfo.Item2]; + war.Call(usr, baseNumber - 1); + SaveWar(war); + await channel.SendMessageAsync($"🔰**{usr}** claimed a base #{baseNumber} for a war against {war.ShortPrint()}").ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync($"💢🔰 {ex.Message}").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ClaimFinish1(IUserMessage umsg, int number, int baseNumber = 0) + { + var channel = (ITextChannel)umsg.Channel; + await FinishClaim(umsg, number, baseNumber - 1, 1); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ClaimFinish2(IUserMessage umsg, int number, int baseNumber = 0) + { + var channel = (ITextChannel)umsg.Channel; + await FinishClaim(umsg, number, baseNumber - 1, 2); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ClaimFinish(IUserMessage umsg, int number, int baseNumber = 0) + { + var channel = (ITextChannel)umsg.Channel; + await FinishClaim(umsg, number, baseNumber - 1); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task EndWar(IUserMessage umsg, int number) + { + var channel = (ITextChannel)umsg.Channel; + + var warsInfo = GetWarInfo(umsg,number); + if (warsInfo == null) + { + await channel.SendMessageAsync("💢🔰 That war does not exist.").ConfigureAwait(false); + return; + } + var war = warsInfo.Item1[warsInfo.Item2]; + war.End(); + SaveWar(war); + await channel.SendMessageAsync($"❗🔰**War against {warsInfo.Item1[warsInfo.Item2].ShortPrint()} ended.**").ConfigureAwait(false); + + var size = warsInfo.Item1[warsInfo.Item2].Size; + warsInfo.Item1.RemoveAt(warsInfo.Item2); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Unclaim(IUserMessage umsg, int number, [Remainder] string otherName = null) + { + var channel = (ITextChannel)umsg.Channel; + + var warsInfo = GetWarInfo(umsg, number); + if (warsInfo == null || warsInfo.Item1.Count == 0) + { + await channel.SendMessageAsync("💢🔰 **That war does not exist.**").ConfigureAwait(false); + return; + } + var usr = + string.IsNullOrWhiteSpace(otherName) ? + umsg.Author.Username : + otherName; + try + { + var war = warsInfo.Item1[warsInfo.Item2]; + var baseNumber = war.Uncall(usr); + SaveWar(war); + await channel.SendMessageAsync($"🔰 @{usr} has **UNCLAIMED** a base #{baseNumber + 1} from a war against {war.ShortPrint()}").ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync($"💢🔰 {ex.Message}").ConfigureAwait(false); + } + } + + private async Task FinishClaim(IUserMessage umsg, int number, int baseNumber, int stars = 3) + { + var channel = (ITextChannel)umsg.Channel; + var warInfo = GetWarInfo(umsg, number); + if (warInfo == null || warInfo.Item1.Count == 0) + { + await channel.SendMessageAsync("💢🔰 **That war does not exist.**").ConfigureAwait(false); + return; + } + var war = warInfo.Item1[warInfo.Item2]; + try + { + if (baseNumber == -1) + { + baseNumber = war.FinishClaim(umsg.Author.Username, stars); + SaveWar(war); + } + else + { + war.FinishClaim(baseNumber, stars); + } + await channel.SendMessageAsync($"❗🔰{umsg.Author.Mention} **DESTROYED** a base #{baseNumber + 1} in a war against {war.ShortPrint()}").ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync($"💢🔰 {ex.Message}").ConfigureAwait(false); + } + } + + private static Tuple, int> GetWarInfo(IUserMessage umsg, int num) + { + var channel = (ITextChannel)umsg.Channel; + //check if there are any wars + List wars = null; + ClashWars.TryGetValue(channel.Guild.Id, out wars); + if (wars == null || wars.Count == 0) + { + return null; + } + // get the number of the war + else if (num < 1 || num > wars.Count) + { + return null; + } + num -= 1; + //get the actual war + return new Tuple, int>(wars, num); + } + + public static async Task CreateWar(string enemyClan, int size, ulong serverId, ulong channelId) + { + using (var uow = DbHandler.UnitOfWork()) + { + var cw = new ClashWar + { + EnemyClan = enemyClan, + Size = size, + Bases = new List(size), + GuildId = serverId, + ChannelId = channelId, + Channel = NadekoBot.Client.GetGuild(serverId) + ?.GetTextChannel(channelId) + }; + cw.Bases.Capacity = size; + for (int i = 0; i < size; i++) + { + cw.Bases.Add(new ClashCaller() + { + CallUser = null, + SequenceNumber = i, + }); + } + Console.WriteLine(cw.Bases.Capacity); + uow.ClashOfClans.Add(cw); + await uow.CompleteAsync(); + return cw; + } + } + + public static void SaveWar(ClashWar cw) + { + if (cw.WarState == ClashWar.StateOfWar.Ended) + { + using (var uow = DbHandler.UnitOfWork()) + { + uow.ClashOfClans.Remove(cw); + uow.CompleteAsync(); + } + return; + } + + + using (var uow = DbHandler.UnitOfWork()) + { + uow.ClashOfClans.Update(cw); + uow.CompleteAsync(); + } + } + } +} diff --git a/src/NadekoBot/Modules/ClashOfClans/Extensions.cs b/src/NadekoBot/Modules/ClashOfClans/Extensions.cs new file mode 100644 index 00000000..9fc998ff --- /dev/null +++ b/src/NadekoBot/Modules/ClashOfClans/Extensions.cs @@ -0,0 +1,130 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Linq; +using System.Text; +using static NadekoBot.Services.Database.Models.ClashWar; + +namespace NadekoBot.Modules.ClashOfClans +{ + public static class Extensions + { + public static void ResetTime(this ClashCaller c) + { + c.TimeAdded = DateTime.UtcNow; + } + + public static void Destroy(this ClashCaller c) + { + c.BaseDestroyed = true; + } + + public static void End(this ClashWar cw) + { + //Ended = true; + cw.WarState = StateOfWar.Ended; + } + + public static void Call(this ClashWar cw, string u, int baseNumber) + { + if (baseNumber < 0 || baseNumber >= cw.Bases.Count) + throw new ArgumentException("Invalid base number"); + if (cw.Bases[baseNumber].CallUser != null) + throw new ArgumentException("That base is already claimed."); + for (var i = 0; i < cw.Bases.Count; i++) + { + if (cw.Bases[i]?.BaseDestroyed == false && cw.Bases[i]?.CallUser == u) + throw new ArgumentException($"@{u} You already claimed base #{i + 1}. You can't claim a new one."); + } + + var cc = cw.Bases[baseNumber]; + cc.CallUser = u.Trim(); + cc.TimeAdded = DateTime.UtcNow; + cc.BaseDestroyed = false; + } + + public static void Start(this ClashWar cw) + { + if (cw.WarState == StateOfWar.Started) + throw new InvalidOperationException("War already started"); + //if (Started) + // throw new InvalidOperationException(); + //Started = true; + cw.WarState = StateOfWar.Started; + cw.StartedAt = DateTime.UtcNow; + foreach (var b in cw.Bases.Where(b => b.CallUser != null)) + { + b.ResetTime(); + } + } + + public static int Uncall(this ClashWar cw, string user) + { + user = user.Trim(); + for (var i = 0; i < cw.Bases.Count; i++) + { + if (cw.Bases[i]?.CallUser != user) continue; + cw.Bases[i].CallUser = null; + return i; + } + throw new InvalidOperationException("You are not participating in that war."); + } + + public static string ShortPrint(this ClashWar cw) => + $"`{cw.EnemyClan}` ({cw.Size} v {cw.Size})"; + + public static string ToPrettyString(this ClashWar cw) + { + var sb = new StringBuilder(); + + sb.AppendLine($"🔰**WAR AGAINST `{cw.EnemyClan}` ({cw.Size} v {cw.Size}) INFO:**"); + if (cw.WarState == StateOfWar.Created) + sb.AppendLine("`not started`"); + var twoHours = new TimeSpan(2, 0, 0); + for (var i = 0; i < cw.Bases.Count; i++) + { + if (cw.Bases[i].CallUser == null) + { + sb.AppendLine($"`{i + 1}.` ❌*unclaimed*"); + } + else + { + if (cw.Bases[i].BaseDestroyed) + { + sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {new string('⭐', cw.Bases[i].Stars)}"); + } + else + { + var left = (cw.WarState == StateOfWar.Started) ? twoHours - (DateTime.UtcNow - cw.Bases[i].TimeAdded) : twoHours; + sb.AppendLine($"`{i + 1}.` ✅ `{cw.Bases[i].CallUser}` {left.Hours}h {left.Minutes}m {left.Seconds}s left"); + } + } + + } + return sb.ToString(); + } + + public static int FinishClaim(this ClashWar cw, string user, int stars = 3) + { + user = user.Trim(); + for (var i = 0; i < cw.Bases.Count; i++) + { + if (cw.Bases[i]?.BaseDestroyed != false || cw.Bases[i]?.CallUser != user) continue; + cw.Bases[i].BaseDestroyed = true; + cw.Bases[i].Stars = stars; + return i; + } + throw new InvalidOperationException($"@{user} You are either not participating in that war, or you already destroyed a base."); + } + + public static void FinishClaim(this ClashWar cw, int index, int stars = 3) + { + if (index < 0 || index > cw.Bases.Count) + throw new ArgumentOutOfRangeException(nameof(index)); + var toFinish = cw.Bases[index]; + if (toFinish.BaseDestroyed != false) throw new InvalidOperationException("That base is already destroyed."); + if (toFinish.CallUser == null) throw new InvalidOperationException("That base is unclaimed."); + toFinish.BaseDestroyed = true; + toFinish.Stars = stars; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs new file mode 100644 index 00000000..32614b05 --- /dev/null +++ b/src/NadekoBot/Modules/CustomReactions/CustomReactions.cs @@ -0,0 +1,261 @@ +using System.Linq; +using System.Threading.Tasks; +using Discord.Commands; +using NadekoBot.Services; +using NadekoBot.Attributes; +using System.Collections.Concurrent; +using NadekoBot.Services.Database.Models; +using Discord; +using NadekoBot.Extensions; + +namespace NadekoBot.Modules.CustomReactions +{ + [NadekoModule("CustomReactions",".")] + public class CustomReactions : DiscordModule + { + public static ConcurrentHashSet GlobalReactions { get; } = new ConcurrentHashSet(); + public static ConcurrentDictionary> GuildReactions { get; } = new ConcurrentDictionary>(); + + static CustomReactions() + { + using (var uow = DbHandler.UnitOfWork()) + { + var items = uow.CustomReactions.GetAll(); + GuildReactions = new ConcurrentDictionary>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => new ConcurrentHashSet(g))); + GlobalReactions = new ConcurrentHashSet(items.Where(g => g.GuildId == null || g.GuildId == 0)); + } + } + public CustomReactions(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + } + + public static async Task TryExecuteCustomReaction(IUserMessage umsg) + { + var channel = umsg.Channel as ITextChannel; + if (channel == null) + return false; + + var content = umsg.Content.Trim().ToLowerInvariant(); + ConcurrentHashSet reactions; + GuildReactions.TryGetValue(channel.Guild.Id, out reactions); + if (reactions != null && reactions.Any()) + { + var reaction = reactions.Where(cr => { + var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%"); + var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant(); + return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger); + }).Shuffle().FirstOrDefault(); + if (reaction != null) + { + try { await channel.SendMessageAsync(reaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { } + return true; + } + } + var greaction = GlobalReactions.Where(cr => + { + var hasTarget = cr.Response.ToLowerInvariant().Contains("%target%"); + var trigger = cr.TriggerWithContext(umsg).Trim().ToLowerInvariant(); + return ((hasTarget && content.StartsWith(trigger + " ")) || content == trigger); + }).Shuffle().FirstOrDefault(); + + if (greaction != null) + { + try { await channel.SendMessageAsync(greaction.ResponseWithContext(umsg)).ConfigureAwait(false); } catch { } + return true; + } + return false; + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task AddCustReact(IUserMessage imsg, string key, [Remainder] string message) + { + var channel = imsg.Channel as ITextChannel; + if (string.IsNullOrWhiteSpace(message) || string.IsNullOrWhiteSpace(key)) + return; + + key = key.ToLowerInvariant(); + + if ((channel == null && !NadekoBot.Credentials.IsOwner(imsg.Author)) || (channel != null && !((IGuildUser)imsg.Author).GuildPermissions.Administrator)) + { + try { await imsg.Channel.SendMessageAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { } + return; + } + + var cr = new CustomReaction() + { + GuildId = channel?.Guild.Id, + IsRegex = false, + Trigger = key, + Response = message, + }; + + using (var uow = DbHandler.UnitOfWork()) + { + uow.CustomReactions.Add(cr); + + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (channel == null) + { + GlobalReactions.Add(cr); + } + else + { + var reactions = GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + reactions.Add(cr); + } + + await imsg.Channel.SendMessageAsync($"`Added new custom reaction {cr.Id}:`\n\t`Trigger:` {key}\n\t`Response:` {message}").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(0)] + public async Task ListCustReact(IUserMessage imsg, int page = 1) + { + var channel = imsg.Channel as ITextChannel; + + if (page < 1 || page > 1000) + return; + ConcurrentHashSet customReactions; + if (channel == null) + customReactions = GlobalReactions; + else + customReactions = GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + + if (customReactions == null || !customReactions.Any()) + await imsg.Channel.SendMessageAsync("`No custom reactions found`").ConfigureAwait(false); + else + await imsg.Channel.SendMessageAsync( + $"`Page {page} of custom reactions:`\n" + + string.Join("\n", customReactions + .OrderBy(cr => cr.Trigger) + .Skip((page - 1) * 20) + .Take(20) + .Select(cr => $"`#{cr.Id}` `Trigger:` {cr.Trigger}"))) + .ConfigureAwait(false); + } + + public enum All + { + All + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(1)] + public async Task ListCustReact(IUserMessage imsg, All x) + { + var channel = imsg.Channel as ITextChannel; + + ConcurrentHashSet customReactions; + if (channel == null) + customReactions = GlobalReactions; + else + customReactions = GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + + if (customReactions == null || !customReactions.Any()) + await imsg.Channel.SendMessageAsync("`No custom reactions found`").ConfigureAwait(false); + else + { + var txtStream = await customReactions.GroupBy(cr => cr.Trigger) + .OrderBy(cr => cr.Key) + .Select(cr => new { Trigger = cr.Key, Responses = cr.Select(y => y.Response).ToList() }) + .ToJson() + .ToStream() + .ConfigureAwait(false); + if (channel == null) // its a private one, just send back + await imsg.Channel.SendFileAsync(txtStream, "customreactions.txt", "List of all custom reactions").ConfigureAwait(false); + else + await ((IGuildUser)imsg.Author).SendFileAsync(txtStream, "customreactions.txt", "List of all custom reactions").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ListCustReactG(IUserMessage imsg, int page = 1) + { + var channel = imsg.Channel as ITextChannel; + if (page < 1 || page > 10000) + return; + ConcurrentHashSet customReactions; + if (channel == null) + customReactions = GlobalReactions; + else + customReactions = GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + + if (customReactions == null || !customReactions.Any()) + await imsg.Channel.SendMessageAsync("`No custom reactions found`").ConfigureAwait(false); + else + await imsg.Channel.SendMessageAsync($"{imsg.Author.Mention}\n`Page {page} of custom reactions (grouped):`\n" + + string.Join("\r\n", customReactions + .GroupBy(cr=>cr.Trigger) + .OrderBy(cr => cr.Key) + .Skip((page - 1) * 20) + .Take(20) + .Select(cr => $"**{cr.Key.Trim().ToLowerInvariant()}** `x{cr.Count()}`"))) + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ShowCustReact(IUserMessage imsg, int id) + { + var channel = imsg.Channel as ITextChannel; + + ConcurrentHashSet customReactions; + if (channel == null) + customReactions = GlobalReactions; + else + customReactions = GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + + var found = customReactions.FirstOrDefault(cr => cr.Id == id); + + if (found == null) + await imsg.Channel.SendMessageAsync("`No custom reaction found with that id.`").ConfigureAwait(false); + else + { + await imsg.Channel.SendMessageAsync($"`Custom reaction #{id}`\n`Trigger:` {found.Trigger}\n`Response:` {found.Response} ```css\n{found.Response}```") + .ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task DelCustReact(IUserMessage imsg, int id) + { + var channel = imsg.Channel as ITextChannel; + + if ((channel == null && !NadekoBot.Credentials.IsOwner(imsg.Author)) || (channel != null && !((IGuildUser)imsg.Author).GuildPermissions.Administrator)) + { + try { await imsg.Channel.SendMessageAsync("Insufficient permissions. Requires Bot ownership for global custom reactions, and Administrator for guild custom reactions."); } catch { } + return; + } + + var success = false; + CustomReaction toDelete; + using (var uow = DbHandler.UnitOfWork()) + { + toDelete = uow.CustomReactions.Get(id); + if (toDelete == null) //not found + return; + + if ((toDelete.GuildId == null || toDelete.GuildId == 0) && channel == null) + { + uow.CustomReactions.Remove(toDelete); + GlobalReactions.RemoveWhere(cr => cr.Id == toDelete.Id); + success = true; + } + else if ((toDelete.GuildId != null && toDelete.GuildId != 0) && channel?.Guild.Id == toDelete.GuildId) + { + uow.CustomReactions.Remove(toDelete); + GuildReactions.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()).RemoveWhere(cr => cr.Id == toDelete.Id); + success = true; + } + if(success) + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (success) + await imsg.Channel.SendMessageAsync("**Successfully deleted custom reaction** " + toDelete.ToString()).ConfigureAwait(false); + else + await imsg.Channel.SendMessageAsync("`Failed to find that custom reaction.`").ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/Modules/CustomReactions/Extensions.cs b/src/NadekoBot/Modules/CustomReactions/Extensions.cs new file mode 100644 index 00000000..f5cb66cc --- /dev/null +++ b/src/NadekoBot/Modules/CustomReactions/Extensions.cs @@ -0,0 +1,52 @@ +using Discord; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; + +namespace NadekoBot.Modules.CustomReactions +{ + public static class Extensions + { + public static Dictionary> responsePlaceholders = new Dictionary>() + { + {"%target%", (ctx, trigger) => { return ctx.Content.Substring(trigger.Length).Trim(); } } + }; + + public static Dictionary> placeholders = new Dictionary>() + { + {"%mention%", (ctx) => { return $"<@{NadekoBot.Client.GetCurrentUser().Id}>"; } }, + {"%user%", (ctx) => { return ctx.Author.Mention; } }, + {"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } } + }; + + private static string ResolveTriggerString(this string str, IUserMessage ctx) + { + foreach (var ph in placeholders) + { + str = str.ToLowerInvariant().Replace(ph.Key, ph.Value(ctx)); + } + return str; + } + + private static string ResolveResponseString(this string str, IUserMessage ctx, string resolvedTrigger) + { + foreach (var ph in placeholders) + { + str = str.Replace(ph.Key.ToLowerInvariant(), ph.Value(ctx)); + } + + foreach (var ph in responsePlaceholders) + { + str = str.Replace(ph.Key.ToLowerInvariant(), ph.Value(ctx, resolvedTrigger)); + } + return str; + } + + public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx) + => cr.Trigger.ResolveTriggerString(ctx); + + public static string ResponseWithContext(this CustomReaction cr, IUserMessage ctx) + => cr.Response.ResolveResponseString(ctx, cr.Trigger.ResolveTriggerString(ctx)); + } +} diff --git a/src/NadekoBot/Modules/DiscordModule.cs b/src/NadekoBot/Modules/DiscordModule.cs new file mode 100644 index 00000000..bb7a6cbf --- /dev/null +++ b/src/NadekoBot/Modules/DiscordModule.cs @@ -0,0 +1,29 @@ +using Discord.Commands; +using NadekoBot.Services; +using NLog; + +namespace NadekoBot.Modules +{ + public class DiscordModule + { + protected ILocalization _l { get; } + protected CommandService _commands { get; } + protected ShardedDiscordClient _client { get; } + protected Logger _log { get; } + protected string _prefix { get; } + + public DiscordModule(ILocalization loc, CommandService cmds, ShardedDiscordClient client) + { + string prefix; + if (NadekoBot.ModulePrefixes.TryGetValue(this.GetType().Name, out prefix)) + _prefix = prefix; + else + _prefix = "?missing_prefix?"; + + _l = loc; + _commands = cmds; + _client = client; + _log = LogManager.GetCurrentClassLogger(); + } + } +} diff --git a/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs b/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs new file mode 100644 index 00000000..9bda0461 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Commands/AnimalRacing.cs @@ -0,0 +1,307 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + [Group] + public class AnimalRacing + { + + public AnimalRacing() + { + } + public static ConcurrentDictionary AnimalRaces = new ConcurrentDictionary(); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Race(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + var ar = new AnimalRace(channel.Guild.Id, channel); + + if (ar.Fail) + await channel.SendMessageAsync("🏁 `Failed starting a race. Another race is probably running.`"); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task JoinRace(IUserMessage umsg, int amount = 0) + { + var channel = (ITextChannel)umsg.Channel; + + if (amount < 0) + amount = 0; + + + AnimalRace ar; + if (!AnimalRaces.TryGetValue(channel.Guild.Id, out ar)) + { + await channel.SendMessageAsync("No race exists on this server"); + return; + } + await ar.JoinRace(umsg.Author as IGuildUser, amount); + } + + public class AnimalRace + { + + private ConcurrentQueue animals { get; } + + public bool Fail { get; set; } + + public List participants = new List(); + private ulong serverId; + private int messagesSinceGameStarted = 0; + private Logger _log { get; } + + public ITextChannel raceChannel { get; set; } + public bool Started { get; private set; } = false; + + public AnimalRace(ulong serverId, ITextChannel ch) + { + this._log = LogManager.GetCurrentClassLogger(); + this.serverId = serverId; + this.raceChannel = ch; + if (!AnimalRaces.TryAdd(serverId, this)) + { + Fail = true; + return; + } + + using (var uow = DbHandler.UnitOfWork()) + { + animals = new ConcurrentQueue(uow.BotConfig.GetOrCreate().RaceAnimals.Select(ra => ra.Icon).Shuffle()); + } + + + var cancelSource = new CancellationTokenSource(); + var token = cancelSource.Token; + var fullgame = CheckForFullGameAsync(token); + Task.Run(async () => + { + try + { + try { await raceChannel.SendMessageAsync($"🏁`Race is starting in 20 seconds or when the room is full. Type {NadekoBot.ModulePrefixes[typeof(Gambling).Name]}jr to join the race.`"); } catch (Exception ex) { _log.Warn(ex); } + var t = await Task.WhenAny(Task.Delay(20000, token), fullgame); + Started = true; + cancelSource.Cancel(); + if (t == fullgame) + { + try { await raceChannel.SendMessageAsync("🏁`Race full, starting right now!`"); } catch (Exception ex) { _log.Warn(ex); } + } + else if (participants.Count > 1) + { + try { await raceChannel.SendMessageAsync("🏁`Game starting with " + participants.Count + " participants.`"); } catch (Exception ex) { _log.Warn(ex); } + } + else + { + try { await raceChannel.SendMessageAsync("🏁`Race failed to start since there was not enough participants.`"); } catch (Exception ex) { _log.Warn(ex); } + var p = participants.FirstOrDefault(); + + if (p != null && p.AmountBet > 0) + await CurrencyHandler.AddCurrencyAsync(p.User, "BetRace", p.AmountBet, false).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 NadekoRandom(); + Participant winner = null; + IUserMessage 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.DeleteAsync(); } catch { } + messagesSinceGameStarted = 0; + try { msg = await raceChannel.SendMessageAsync(text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + } + else + { + try { await msg.ModifyAsync(m => m.Content = text).ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + } + + await Task.Delay(2500); + } + } + catch { } + finally + { + NadekoBot.Client.MessageReceived -= Client_MessageReceived; + } + + if (winner.AmountBet > 0) + { + var wonAmount = winner.AmountBet * (participants.Count - 1); + + await CurrencyHandler.AddCurrencyAsync(winner.User, "Won a Race", wonAmount, false).ConfigureAwait(false); + await raceChannel.SendMessageAsync($"🏁 {winner.User.Mention} as {winner.Animal} **Won the race and {wonAmount}{CurrencySign}!**").ConfigureAwait(false); + } + else + { + await raceChannel.SendMessageAsync($"🏁 {winner.User.Mention} as {winner.Animal} **Won the race!**"); + } + + } + + private Task Client_MessageReceived(IMessage imsg) + { + var msg = imsg as IUserMessage; + if (msg == null) + return Task.CompletedTask; + if (msg.IsAuthor() || !(imsg.Channel is ITextChannel) || imsg.Channel != raceChannel) + return Task.CompletedTask; + messagesSinceGameStarted++; + return Task.CompletedTask; + } + + private async Task CheckForFullGameAsync(CancellationToken cancelToken) + { + while (animals.Count > 0) + { + await Task.Delay(100, cancelToken); + } + } + + public async Task JoinRace(IGuildUser u, int amount = 0) + { + var animal = ""; + if (!animals.TryDequeue(out animal)) + { + await raceChannel.SendMessageAsync($"{u.Mention} `There is no running race on this server.`"); + return; + } + var p = new Participant(u, animal, amount); + if (participants.Contains(p)) + { + await raceChannel.SendMessageAsync($"{u.Mention} `You already joined this race.`"); + return; + } + if (Started) + { + await raceChannel.SendMessageAsync($"{u.Mention} `Race is already started`"); + return; + } + if (amount > 0) + if (!await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)u, "BetRace", amount, true).ConfigureAwait(false)) + { + try { await raceChannel.SendMessageAsync($"{u.Mention} You don't have enough {Gambling.CurrencyName}s.").ConfigureAwait(false); } catch { } + return; + } + participants.Add(p); + await raceChannel.SendMessageAsync($"{u.Mention} **joined the race as a {p.Animal}" + (amount > 0 ? $" and bet {amount} {CurrencySign}!**" : "**")); + } + } + + public class Participant + { + public IGuildUser 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(IGuildUser 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`"; + } + + } + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Commands/DiceRollCommand.cs b/src/NadekoBot/Modules/Gambling/Commands/DiceRollCommand.cs new file mode 100644 index 00000000..dc075964 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Commands/DiceRollCommand.cs @@ -0,0 +1,280 @@ +using Discord; +using Discord.Commands; +using ImageSharp; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + [Group] + public class DriceRollCommands + { + private Regex dndRegex { get; } = new Regex(@"^(?\d+)d(?\d+)(?:\+(?\d+))?(?:\-(?\d+))?$", RegexOptions.Compiled); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Roll(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + if (channel == null) + return; + var rng = new NadekoRandom(); + var gen = rng.Next(1, 101); + + var num1 = gen / 10; + var num2 = gen % 10; + var imageStream = await Task.Run(() => + { + try + { + var ms = new MemoryStream(); + new[] { GetDice(num1), GetDice(num2) }.Merge().SaveAsPng(ms); + ms.Position = 0; + return ms; + } + catch { return new MemoryStream(); } + }); + + await channel.SendFileAsync(imageStream, "dice.png", $"{umsg.Author.Mention} rolled " + Format.Code(gen.ToString())).ConfigureAwait(false); + } + //todo merge into internallDndRoll and internalRoll + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(1)] + public async Task Roll(IUserMessage umsg, string arg) + { + var channel = (ITextChannel)umsg.Channel; + if (channel == null) + return; + + var ordered = true; + var rng = new NadekoRandom(); + Match match; + if ((match = dndRegex.Match(arg)).Length != 0) + { + int n1; + int n2; + if (int.TryParse(match.Groups["n1"].ToString(), out n1) && + int.TryParse(match.Groups["n2"].ToString(), out n2) && + n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0) + { + var add = 0; + var sub = 0; + int.TryParse(match.Groups["add"].Value, out add); + int.TryParse(match.Groups["sub"].Value, out sub); + + var arr = new int[n1]; + for (int i = 0; i < n1; i++) + { + arr[i] = rng.Next(1, n2 + 1) + add - sub; + } + var elemCnt = 0; + await channel.SendMessageAsync($"{umsg.Author.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} `1 to {n2}` +`{add}` -`{sub}`.\n`Result:` " + string.Join(", ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(0)] + public async Task Roll(IUserMessage umsg, int num) + { + var channel = (ITextChannel)umsg.Channel; + if (channel == null) + return; + + var ordered = true; + + if (num < 1 || num > 30) + { + await channel.SendMessageAsync("Invalid number specified. You can roll up to 1-30 dice at a time.").ConfigureAwait(false); + return; + } + + var rng = new NadekoRandom(); + + var dice = new List(num); + var values = new List(num); + for (var i = 0; i < num; i++) + { + var randomNumber = rng.Next(1, 7); + var toInsert = dice.Count; + if (ordered) + { + if (randomNumber == 6 || dice.Count == 0) + toInsert = 0; + else if (randomNumber != 1) + for (var j = 0; j < dice.Count; j++) + { + if (values[j] < randomNumber) + { + toInsert = j; + break; + } + } + } + else + { + toInsert = dice.Count; + } + dice.Insert(toInsert, GetDice(randomNumber)); + values.Insert(toInsert, randomNumber); + } + + var bitmap = dice.Merge(); + var ms = new MemoryStream(); + bitmap.SaveAsPng(ms); + ms.Position = 0; + await channel.SendFileAsync(ms, "dice.png", $"{umsg.Author.Mention} rolled {values.Count} {(values.Count == 1 ? "die" : "dice")}. Total: **{values.Sum()}** Average: **{(values.Sum() / (1.0f * values.Count)).ToString("N2")}**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Rolluo(IUserMessage umsg, string arg) + { + var channel = (ITextChannel)umsg.Channel; + if (channel == null) + return; + + var ordered = false; + var rng = new NadekoRandom(); + Match match; + if ((match = dndRegex.Match(arg)).Length != 0) + { + int n1; + int n2; + if (int.TryParse(match.Groups["n1"].ToString(), out n1) && + int.TryParse(match.Groups["n2"].ToString(), out n2) && + n1 <= 50 && n2 <= 100000 && n1 > 0 && n2 > 0) + { + var add = 0; + var sub = 0; + int.TryParse(match.Groups["add"].Value, out add); + int.TryParse(match.Groups["sub"].Value, out sub); + + var arr = new int[n1]; + for (int i = 0; i < n1; i++) + { + arr[i] = rng.Next(1, n2 + 1) + add - sub; + } + var elemCnt = 0; + await channel.SendMessageAsync($"{umsg.Author.Mention} rolled {n1} {(n1 == 1 ? "die" : "dice")} `1 to {n2}` +`{add}` -`{sub}`.\n`Result:` " + string.Join(", ", (ordered ? arr.OrderBy(x => x).AsEnumerable() : arr).Select(x => elemCnt++ % 2 == 0 ? $"**{x}**" : x.ToString()))).ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Rolluo(IUserMessage umsg, int num) + { + var channel = (ITextChannel)umsg.Channel; + if (channel == null) + return; + + var ordered = false; + + if (num < 1 || num > 30) + { + await channel.SendMessageAsync("Invalid number specified. You can roll up to 1-30 dice at a time.").ConfigureAwait(false); + return; + } + + var rng = new NadekoRandom(); + + var dice = new List(num); + var values = new List(num); + for (var i = 0; i < num; i++) + { + var randomNumber = rng.Next(1, 7); + var toInsert = dice.Count; + if (ordered) + { + if (randomNumber == 6 || dice.Count == 0) + toInsert = 0; + else if (randomNumber != 1) + for (var j = 0; j < dice.Count; j++) + { + if (values[j] < randomNumber) + { + toInsert = j; + break; + } + } + } + else + { + toInsert = dice.Count; + } + dice.Insert(toInsert, GetDice(randomNumber)); + values.Insert(toInsert, randomNumber); + } + + var bitmap = dice.Merge(); + var ms = new MemoryStream(); + bitmap.SaveAsPng(ms); + ms.Position = 0; + await channel.SendFileAsync(ms, "dice.png", $"{umsg.Author.Mention} rolled {values.Count} {(values.Count == 1 ? "die" : "dice")}. Total: **{values.Sum()}** Average: **{(values.Sum() / (1.0f * values.Count)).ToString("N2")}**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task NRoll(IUserMessage umsg, [Remainder] string range) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + int rolled; + if (range.Contains("-")) + { + var arr = range.Split('-') + .Take(2) + .Select(int.Parse) + .ToArray(); + if (arr[0] > arr[1]) + throw new ArgumentException("First argument should be bigger than the second one."); + rolled = new NadekoRandom().Next(arr[0], arr[1] + 1); + } + else + { + rolled = new NadekoRandom().Next(0, int.Parse(range) + 1); + } + + await channel.SendMessageAsync($"{umsg.Author.Mention} rolled **{rolled}**.").ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync($":anger: {ex.Message}").ConfigureAwait(false); + } + } + + private Image GetDice(int num) + { + const string pathToImage = "data/images/dice"; + if (num != 10) + { + using (var stream = File.OpenRead(Path.Combine(pathToImage, $"{num}.png"))) + return new Image(stream); + } + + using (var one = File.OpenRead(Path.Combine(pathToImage, "1.png"))) + using (var zero = File.OpenRead(Path.Combine(pathToImage, "0.png"))) + { + Image imgOne = new Image(one); + Image imgZero = new Image(zero); + + return new[] { imgOne, imgZero }.Merge(); + } + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Commands/DrawCommand.cs b/src/NadekoBot/Modules/Gambling/Commands/DrawCommand.cs new file mode 100644 index 00000000..eca29d75 --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Commands/DrawCommand.cs @@ -0,0 +1,82 @@ +using Discord; +using Discord.Commands; +using ImageSharp; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Gambling.Models; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + [Group] + public class DrawCommands + { + private static readonly ConcurrentDictionary AllDecks = new ConcurrentDictionary(); + + + public DrawCommands() + { + _log = LogManager.GetCurrentClassLogger(); + } + + private const string cardsPath = "data/images/cards"; + private Logger _log { get; } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Draw(IUserMessage msg, int num = 1) + { + var channel = (ITextChannel)msg.Channel; + var cards = AllDecks.GetOrAdd(channel.Guild, (s) => new Cards()); + var images = new List(); + var cardObjects = new List(); + if (num > 5) num = 5; + for (var i = 0; i < num; i++) + { + if (cards.CardPool.Count == 0 && i != 0) + { + try { await channel.SendMessageAsync("No more cards in a deck.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + break; + } + var currentCard = cards.DrawACard(); + cardObjects.Add(currentCard); + using (var stream = File.OpenRead(Path.Combine(cardsPath, currentCard.ToString().ToLowerInvariant()+ ".jpg").Replace(' ','_'))) + images.Add(new Image(stream)); + } + MemoryStream bitmapStream = new MemoryStream(); + images.Merge().SaveAsPng(bitmapStream); + bitmapStream.Position = 0; + //todo CARD NAMES? + var toSend = $"{msg.Author.Mention}"; + if (cardObjects.Count == 5) + toSend += $" drew `{Cards.GetHandValue(cardObjects)}`"; + + await channel.SendFileAsync(bitmapStream, images.Count + " cards.jpg", toSend).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ShuffleDeck(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + AllDecks.AddOrUpdate(channel.Guild, + (g) => new Cards(), + (g, c) => + { + c.Restart(); + return c; + }); + + await channel.SendMessageAsync("`Deck reshuffled.`").ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs b/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs new file mode 100644 index 00000000..0ea7bd5e --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Commands/FlipCoinCommand.cs @@ -0,0 +1,113 @@ +using Discord; +using Discord.Commands; +using ImageSharp; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using System; +using System.IO; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Gambling +{ + public partial class Gambling + { + [Group] + public class FlipCoinCommands + { + NadekoRandom rng { get; } = new NadekoRandom(); + private const string headsPath = "data/images/coins/heads.png"; + private const string tailsPath = "data/images/coins/tails.png"; + public FlipCoinCommands() { } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Flip(IUserMessage imsg, int count = 1) + { + var channel = (ITextChannel)imsg.Channel; + if (count == 1) + { + if (rng.Next(0, 2) == 1) + await channel.SendFileAsync(headsPath, $"{imsg.Author.Mention} rolled " + Format.Code("Heads") + ".").ConfigureAwait(false); + else + await channel.SendFileAsync(tailsPath, $"{imsg.Author.Mention} rolled " + Format.Code("Tails") + ".").ConfigureAwait(false); + return; + } + if (count > 10 || count < 1) + { + await channel.SendMessageAsync("`Invalid number specified. You can flip 1 to 10 coins.`"); + return; + } + var imgs = new Image[count]; + for (var i = 0; i < count; i++) + { + imgs[i] = rng.Next(0, 10) < 5 ? + new Image(File.OpenRead(headsPath)) : + new Image(File.OpenRead(tailsPath)); + } + await channel.SendFileAsync(imgs.Merge().ToStream(), $"{count} coins.png").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Betflip(IUserMessage umsg, int amount, string guess) + { + var channel = (ITextChannel)umsg.Channel; + var guildUser = (IGuildUser)umsg.Author; + var guessStr = guess.Trim().ToUpperInvariant(); + if (guessStr != "H" && guessStr != "T" && guessStr != "HEADS" && guessStr != "TAILS") + return; + + if (amount < 3) + { + await channel.SendMessageAsync($"You can't bet less than 3{Gambling.CurrencySign}.") + .ConfigureAwait(false); + return; + } + // todo update this + long userFlowers; + using (var uow = DbHandler.UnitOfWork()) + { + userFlowers = uow.Currency.GetOrCreate(umsg.Author.Id).Amount; + } + + if (userFlowers < amount) + { + await channel.SendMessageAsync($"{umsg.Author.Mention} You don't have enough {Gambling.CurrencyPluralName}. You only have {userFlowers}{Gambling.CurrencySign}.").ConfigureAwait(false); + return; + } + + await CurrencyHandler.RemoveCurrencyAsync(guildUser, "Betflip Gamble", amount, false).ConfigureAwait(false); + //heads = true + //tails = false + + var isHeads = guessStr == "HEADS" || guessStr == "H"; + bool result = false; + string imgPathToSend; + if (rng.Next(0, 2) == 1) + { + imgPathToSend = headsPath; + result = true; + } + else + { + imgPathToSend = tailsPath; + } + + string str; + if (isHeads == result) + { + var toWin = (int)Math.Round(amount * 1.8); + str = $"{umsg.Author.Mention}`You guessed it!` You won {toWin}{Gambling.CurrencySign}"; + await CurrencyHandler.AddCurrencyAsync((IGuildUser)umsg.Author, "Betflip Gamble", toWin, false).ConfigureAwait(false); + } + else + { + str = $"{umsg.Author.Mention}`Better luck next time.`"; + } + + await channel.SendFileAsync(imgPathToSend, str).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/NadekoBot/Modules/Gambling/Helpers/Cards.cs b/src/NadekoBot/Modules/Gambling/Commands/Models/Cards.cs similarity index 97% rename from NadekoBot/Modules/Gambling/Helpers/Cards.cs rename to src/NadekoBot/Modules/Gambling/Commands/Models/Cards.cs index 8408535e..2c2624c6 100644 --- a/NadekoBot/Modules/Gambling/Helpers/Cards.cs +++ b/src/NadekoBot/Modules/Gambling/Commands/Models/Cards.cs @@ -1,8 +1,10 @@ -using System; +using NadekoBot.Extensions; +using NadekoBot.Services; +using System; using System.Collections.Generic; using System.Linq; -namespace NadekoBot.Modules.Gambling.Helpers +namespace NadekoBot.Modules.Gambling.Models { public class Cards { @@ -114,7 +116,7 @@ namespace NadekoBot.Modules.Gambling.Helpers } } } - private Random r = new Random(); + private Random r = new NadekoRandom(); /// /// Take a card from the pool, you either take it from the top if the deck is shuffled, or from a random place if the deck is in the default order. /// @@ -143,7 +145,7 @@ namespace NadekoBot.Modules.Gambling.Helpers private void Shuffle() { if (cardPool.Count <= 1) return; - var orderedPool = cardPool.OrderBy(x => r.Next()); + var orderedPool = cardPool.Shuffle(); cardPool = cardPool as List ?? orderedPool.ToList(); } public override string ToString() => string.Concat(cardPool.Select(c => c.ToString())) + Environment.NewLine; diff --git a/src/NadekoBot/Modules/Gambling/Gambling.cs b/src/NadekoBot/Modules/Gambling/Gambling.cs new file mode 100644 index 00000000..1ab94b4f --- /dev/null +++ b/src/NadekoBot/Modules/Gambling/Gambling.cs @@ -0,0 +1,242 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NadekoBot.Services; +using Discord.WebSocket; +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; +using NadekoBot.Services.Database; + +namespace NadekoBot.Modules.Gambling +{ + [NadekoModule("Gambling", "$")] + public partial class Gambling : DiscordModule + { + public static string CurrencyName { get; set; } + public static string CurrencyPluralName { get; set; } + public static string CurrencySign { get; set; } + + public Gambling(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.BotConfig.GetOrCreate(); + + CurrencyName = conf.CurrencyName; + CurrencySign = conf.CurrencySign; + CurrencyPluralName = conf.CurrencyPluralName; + } + } + + public static long GetCurrency(ulong id) + { + using (var uow = DbHandler.UnitOfWork()) + { + return uow.Currency.GetUserCurrency(id); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Raffle(IUserMessage umsg, [Remainder] IRole role = null) + { + var channel = (ITextChannel)umsg.Channel; + + role = role ?? channel.Guild.EveryoneRole; + + var members = role.Members().Where(u => u.Status != UserStatus.Offline && u.Status != UserStatus.Unknown); + var membersArray = members as IUser[] ?? members.ToArray(); + var usr = membersArray[new NadekoRandom().Next(0, membersArray.Length)]; + await channel.SendMessageAsync($"🎟 Raffled user: **{usr.Username}#{usr.Discriminator}** ID: `{usr.Id}`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(0)] + public async Task Cash(IUserMessage umsg, [Remainder] IUser user = null) + { + var channel = umsg.Channel; + + user = user ?? umsg.Author; + + await channel.SendMessageAsync($"{user.Username} has {GetCurrency(user.Id)} {CurrencySign}").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [Priority(1)] + public async Task Cash(IUserMessage umsg, ulong userId) + { + var channel = umsg.Channel; + + await channel.SendMessageAsync($"`{userId}` has {GetCurrency(userId)} {CurrencySign}").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Give(IUserMessage umsg, long amount, [Remainder] IGuildUser receiver) + { + var channel = (ITextChannel)umsg.Channel; + if (amount <= 0 || umsg.Author.Id == receiver.Id) + return; + var success = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)umsg.Author, $"Gift to {receiver.Username} ({receiver.Id}).", amount, true).ConfigureAwait(false); + if (!success) + { + await channel.SendMessageAsync($"{umsg.Author.Mention} You don't have enough {Gambling.CurrencyPluralName}.").ConfigureAwait(false); + return; + } + await CurrencyHandler.AddCurrencyAsync(receiver, $"Gift from {umsg.Author.Username} ({umsg.Author.Id}).", amount, true).ConfigureAwait(false); + await channel.SendMessageAsync($"{umsg.Author.Mention} successfully sent {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} to {receiver.Mention}!").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + [Priority(2)] + public Task Award(IUserMessage umsg, int amount, [Remainder] IGuildUser usr) => + Award(umsg, amount, usr.Id); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + [Priority(1)] + public async Task Award(IUserMessage umsg, int amount, ulong usrId) + { + var channel = (ITextChannel)umsg.Channel; + + if (amount <= 0) + return; + + await CurrencyHandler.AddCurrencyAsync(usrId, $"Awarded by bot owner. ({umsg.Author.Username}/{umsg.Author.Id})", amount).ConfigureAwait(false); + + await channel.SendMessageAsync($"{umsg.Author.Mention} successfully awarded {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} to <@{usrId}>!").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + [Priority(0)] + public async Task Award(IUserMessage umsg, int amount, [Remainder] IRole role) + { + var channel = (ITextChannel)umsg.Channel; + var users = channel.Guild.GetUsers() + .Where(u => u.Roles.Contains(role)) + .ToList(); + await Task.WhenAll(users.Select(u => CurrencyHandler.AddCurrencyAsync(u.Id, + $"Awarded by bot owner to **{role.Name}** role. ({umsg.Author.Username}/{umsg.Author.Id})", + amount))) + .ConfigureAwait(false); + + await channel.SendMessageAsync($"Awarded `{amount}` {Gambling.CurrencyPluralName} to `{users.Count}` users from `{role.Name}` role.") + .ConfigureAwait(false); + + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Take(IUserMessage umsg, long amount, [Remainder] IGuildUser user) + { + var channel = (ITextChannel)umsg.Channel; + if (amount <= 0) + return; + + if(await CurrencyHandler.RemoveCurrencyAsync(user, $"Taken by bot owner.({umsg.Author.Username}/{umsg.Author.Id})", amount, true).ConfigureAwait(false)) + await channel.SendMessageAsync($"{umsg.Author.Mention} successfully took {amount} {(amount == 1? Gambling.CurrencyName : Gambling.CurrencyPluralName)} from {user}!").ConfigureAwait(false); + else + await channel.SendMessageAsync($"{umsg.Author.Mention} was unable to take {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} from {user} because the user doesn't have that much {Gambling.CurrencyPluralName}!").ConfigureAwait(false); + } + + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Take(IUserMessage umsg, long amount, [Remainder] ulong usrId) + { + var channel = (ITextChannel)umsg.Channel; + if (amount <= 0) + return; + + if(await CurrencyHandler.RemoveCurrencyAsync(usrId, $"Taken by bot owner.({umsg.Author.Username}/{umsg.Author.Id})", amount).ConfigureAwait(false)) + await channel.SendMessageAsync($"{umsg.Author.Mention} successfully took {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} from <@{usrId}>!").ConfigureAwait(false); + else + await channel.SendMessageAsync($"{umsg.Author.Mention} was unable to take {amount} {(amount == 1 ? Gambling.CurrencyName : Gambling.CurrencyPluralName)} from `{usrId}` because the user doesn't have that much {Gambling.CurrencyPluralName}!").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task BetRoll(IUserMessage umsg, long amount) + { + var channel = (ITextChannel)umsg.Channel; + + if (amount < 1) + return; + + var guildUser = (IGuildUser)umsg.Author; + + long userFlowers; + using (var uow = DbHandler.UnitOfWork()) + { + userFlowers = uow.Currency.GetOrCreate(umsg.Author.Id).Amount; + } + + if (userFlowers < amount) + { + await channel.SendMessageAsync($"{guildUser.Mention} You don't have enough {Gambling.CurrencyPluralName}. You only have {userFlowers}{Gambling.CurrencySign}.").ConfigureAwait(false); + return; + } + + await CurrencyHandler.RemoveCurrencyAsync(guildUser, "Betroll Gamble", amount, false).ConfigureAwait(false); + + var rng = new NadekoRandom().Next(0, 101); + var str = $"{guildUser.Mention} `You rolled {rng}.` "; + if (rng < 67) + { + str += "Better luck next time."; + } + else if (rng < 91) + { + str += $"Congratulations! You won {amount * 2}{Gambling.CurrencySign} for rolling above 66"; + await CurrencyHandler.AddCurrencyAsync(guildUser, "Betroll Gamble", amount * 2, false).ConfigureAwait(false); + } + else if (rng < 100) + { + str += $"Congratulations! You won {amount * 3}{Gambling.CurrencySign} for rolling above 90."; + await CurrencyHandler.AddCurrencyAsync(guildUser, "Betroll Gamble", amount * 3, false).ConfigureAwait(false); + } + else + { + str += $"👑 Congratulations! You won {amount * 10}{Gambling.CurrencySign} for rolling **100**. 👑"; + await CurrencyHandler.AddCurrencyAsync(guildUser, "Betroll Gamble", amount * 10, false).ConfigureAwait(false); + } + + await channel.SendMessageAsync(str).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Leaderboard(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + IEnumerable richest; + using (var uow = DbHandler.UnitOfWork()) + { + richest = uow.Currency.GetTopRichest(10); + } + if (!richest.Any()) + return; + await channel.SendMessageAsync( + richest.Aggregate(new StringBuilder( +$@"```xl +┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓ +┃ Id ┃ $$$ ┃ +"), + (cur, cs) => cur.AppendLine($@"┣━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━┫ +┃{(channel.Guild.GetUser(cs.UserId)?.Username.TrimTo(18, true) ?? cs.UserId.ToString()),-20} ┃ {cs.Amount,6} ┃") + ).ToString() + "┗━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━━┛```").ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs b/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs new file mode 100644 index 00000000..39c95425 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/CleverBotCommands.cs @@ -0,0 +1,119 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using NLog; +using Services.CleverBotApi; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + [Group] + public class CleverBotCommands + { + private static Logger _log { get; } + + class CleverAnswer { + public string Status { get; set; } + public string Response { get; set; } + } + //user#discrim is the key + public static ConcurrentHashSet ChannelsInConversation { get; } = new ConcurrentHashSet(); + public static ConcurrentDictionary CleverbotGuilds { get; } = new ConcurrentDictionary(); + + static CleverBotCommands() + { + _log = LogManager.GetCurrentClassLogger(); + + using (var uow = DbHandler.UnitOfWork()) + { + var bot = ChatterBotFactory.Create(ChatterBotType.CLEVERBOT); + CleverbotGuilds = new ConcurrentDictionary( + uow.GuildConfigs.GetAll() + .Where(gc => gc.CleverbotEnabled) + .ToDictionary(gc => gc.GuildId, gc => bot.CreateSession())); + } + } + + public static async Task TryAsk(IUserMessage msg) { + var channel = msg.Channel as ITextChannel; + + if (channel == null) + return false; + + ChatterBotSession cleverbot; + if (!CleverbotGuilds.TryGetValue(channel.Guild.Id, out cleverbot)) + return false; + + var nadekoId = NadekoBot.Client.GetCurrentUser().Id; + var normalMention = $"<@{nadekoId}> "; + var nickMention = $"<@!{nadekoId}> "; + string message; + if (msg.Content.StartsWith(normalMention)) + { + message = msg.Content.Substring(normalMention.Length).Trim(); + } + else if (msg.Content.StartsWith(nickMention)) + { + message = msg.Content.Substring(nickMention.Length).Trim(); + } + else + { + return false; + } + + await msg.Channel.TriggerTypingAsync().ConfigureAwait(false); + + var response = await cleverbot.Think(message).ConfigureAwait(false); + try + { + await msg.Channel.SendMessageAsync(response).ConfigureAwait(false); + } + catch (Exception ex) + { + _log.Warn(ex, "Eror sending response"); + await msg.Channel.SendMessageAsync(msg.Author.Mention+" "+response).ConfigureAwait(false); // try twice :\ + } + return true; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(ChannelPermission.ManageMessages)] + public async Task Cleverbot(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + ChatterBotSession throwaway; + if (CleverbotGuilds.TryRemove(channel.Guild.Id, out throwaway)) + { + using (var uow = DbHandler.UnitOfWork()) + { + uow.GuildConfigs.SetCleverbotEnabled(channel.Guild.Id, false); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{imsg.Author.Mention} `Disabled cleverbot on this server.`").ConfigureAwait(false); + return; + } + + var cleverbot = ChatterBotFactory.Create(ChatterBotType.CLEVERBOT); + var session = cleverbot.CreateSession(); + + CleverbotGuilds.TryAdd(channel.Guild.Id, session); + + using (var uow = DbHandler.UnitOfWork()) + { + uow.GuildConfigs.SetCleverbotEnabled(channel.Guild.Id, true); + await uow.CompleteAsync().ConfigureAwait(false); + } + + await channel.SendMessageAsync($"{imsg.Author.Mention} `Enabled cleverbot on this server.`").ConfigureAwait(false); + } + } + } +} diff --git a/NadekoBot/Modules/Games/Commands/Leet.cs b/src/NadekoBot/Modules/Games/Commands/LeetCommands.cs similarity index 91% rename from NadekoBot/Modules/Games/Commands/Leet.cs rename to src/NadekoBot/Modules/Games/Commands/LeetCommands.cs index e71a80d0..1a7657b4 100644 --- a/NadekoBot/Modules/Games/Commands/Leet.cs +++ b/src/NadekoBot/Modules/Games/Commands/LeetCommands.cs @@ -1,26 +1,37 @@ -using Discord.Commands; -using NadekoBot.Classes; +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; using NadekoBot.Extensions; using System.Text; +using System.Threading.Tasks; -//taken from -//http://www.codeproject.com/Tips/207582/L-t-Tr-nsl-t-r-Leet-Translator (thanks) +// taken from +// http://www.codeproject.com/Tips/207582/L-t-Tr-nsl-t-r-Leet-Translator (thanks) // because i don't want to waste my time on this cancerous command -namespace NadekoBot.Modules.Games.Commands +namespace NadekoBot.Modules.Games { - internal class Leet : DiscordCommand + public partial class Games { - public Leet(DiscordModule module) : base(module) + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Leet(IUserMessage umsg, int level, [Remainder] string text = null) { + var channel = (ITextChannel)umsg.Channel; + + text = text.Trim(); + if (string.IsNullOrWhiteSpace(text)) + return; + await channel.SendMessageAsync(ToLeet(text, level)).ConfigureAwait(false); } + /// /// Translate text to Leet - Extension methods for string class /// /// Orginal text /// Degree of translation (1 - 3) /// Leet translated text - public static string ToLeet(string text, int degree = 1) => + private static string ToLeet(string text, int degree = 1) => Translate(text, degree); /// @@ -29,7 +40,7 @@ namespace NadekoBot.Modules.Games.Commands /// Orginal text /// Degree of translation (1 - 3) /// Leet translated text - public static string Translate(string text, int degree = 1) + private static string Translate(string text, int degree = 1) { if (degree > 6) degree = 6; @@ -294,24 +305,5 @@ namespace NadekoBot.Modules.Games.Commands } return sb.ToString().TrimTo(1995); // Return result. } - - internal override void Init(CommandGroupBuilder cgb) - { - cgb.CreateCommand(Module.Prefix + "leet") - .Description($"Converts a text to leetspeak with 6 (1-6) severity levels | `{Module.Prefix}leet 3 Hello`") - .Parameter("level", ParameterType.Required) - .Parameter("text", ParameterType.Unparsed) - .Do(async e => - { - var text = e.GetArg("text")?.Trim(); - var levelStr = e.GetArg("level")?.Trim(); - int level; - if (!int.TryParse(levelStr, out level)) - return; - if (string.IsNullOrWhiteSpace(text)) - return; - await e.Channel.SendMessage(ToLeet(text, level)).ConfigureAwait(false); - }); - } } } diff --git a/src/NadekoBot/Modules/Games/Commands/Models/TypingArticle.cs b/src/NadekoBot/Modules/Games/Commands/Models/TypingArticle.cs new file mode 100644 index 00000000..e5697772 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/Models/TypingArticle.cs @@ -0,0 +1,8 @@ +namespace NadekoBot.Modules.Games.Commands.Models +{ + public class TypingArticle + { + public string Title { get; set; } + public string Text { get; set; } + } +} diff --git a/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs new file mode 100644 index 00000000..893cc1a0 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/PlantAndPickCommands.cs @@ -0,0 +1,211 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games +{ + //todo make currency generation change and cooldown modifyable + //only by bot owner through commands + public partial class Games + { + /// + /// Flower picking/planting idea is given to me by its + /// inceptor Violent Crumble from Game Developers League discord server + /// (he has !cookie and !nom) Thanks a lot Violent! + /// Check out GDL (its a growing gamedev community): + /// https://discord.gg/0TYNJfCU4De7YIk8 + /// + [Group] + public class PlantPickCommands + { + private Random rng; + + private ConcurrentHashSet generationChannels = new ConcurrentHashSet(); + //channelid/message + private ConcurrentDictionary> plantedFlowers = new ConcurrentDictionary>(); + //channelId/last generation + private ConcurrentDictionary lastGenerations = new ConcurrentDictionary(); + + private float chance; + private int cooldown; + private Logger _log { get; } + + public PlantPickCommands() + { + _log = LogManager.GetCurrentClassLogger(); + NadekoBot.Client.MessageReceived += PotentialFlowerGeneration; + rng = new NadekoRandom(); + + using (var uow = DbHandler.UnitOfWork()) + { + var conf = uow.BotConfig.GetOrCreate(); + var x = + generationChannels = new ConcurrentHashSet(NadekoBot.AllGuildConfigs + .SelectMany(c => c.GenerateCurrencyChannelIds.Select(obj=>obj.ChannelId))); + chance = conf.CurrencyGenerationChance; + cooldown = conf.CurrencyGenerationCooldown; + } + } + + private Task PotentialFlowerGeneration(IMessage imsg) + { + var msg = imsg as IUserMessage; + if (msg == null || msg.IsAuthor() || msg.Author.IsBot) + return Task.CompletedTask; + + var channel = imsg.Channel as ITextChannel; + if (channel == null) + return Task.CompletedTask; + + if (!generationChannels.Contains(channel.Id)) + return Task.CompletedTask; + + var t = Task.Run(async () => + { + var lastGeneration = lastGenerations.GetOrAdd(channel.Id, DateTime.MinValue); + + if (DateTime.Now - TimeSpan.FromSeconds(cooldown) < lastGeneration) //recently generated in this channel, don't generate again + return; + + var num = rng.Next(1, 101) + chance * 100; + + if (num > 100) + { + lastGenerations.AddOrUpdate(channel.Id, DateTime.Now, (id, old) => DateTime.Now); + try + { + var sent = await channel.SendFileAsync( + GetRandomCurrencyImagePath(), + $"❗ A random { Gambling.Gambling.CurrencyName } appeared! Pick it up by typing `{NadekoBot.ModulePrefixes[typeof(Games).Name]}pick`") + .ConfigureAwait(false); + plantedFlowers.AddOrUpdate(channel.Id, new List() { sent }, (id, old) => { old.Add(sent); return old; }); + } + catch { } + + } + }); + return Task.CompletedTask; + } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Pick(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + if (!channel.Guild.GetCurrentUser().GetPermissions(channel).ManageMessages) + { + await channel.SendMessageAsync("`I need manage channel permissions in order to process this command.`").ConfigureAwait(false); + return; + } + + List msgs; + + try { await imsg.DeleteAsync().ConfigureAwait(false); } catch { } + if (!plantedFlowers.TryRemove(channel.Id, out msgs)) + return; + + await Task.WhenAll(msgs.Select(toDelete => toDelete.DeleteAsync())).ConfigureAwait(false); + + await CurrencyHandler.AddCurrencyAsync((IGuildUser)imsg.Author, "Picked flower(s).", msgs.Count, false).ConfigureAwait(false); + var msg = await channel.SendMessageAsync($"**{imsg.Author.Username}** picked {msgs.Count}{Gambling.Gambling.CurrencySign}!").ConfigureAwait(false); + var t = Task.Run(async () => + { + await Task.Delay(10000).ConfigureAwait(false); + try { await msg.DeleteAsync().ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + }); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Plant(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + var removed = await CurrencyHandler.RemoveCurrencyAsync((IGuildUser)imsg.Author, "Planted a flower.", 1, false).ConfigureAwait(false); + if (!removed) + { + await channel.SendMessageAsync($"You don't have any {Gambling.Gambling.CurrencyPluralName}.").ConfigureAwait(false); + return; + } + + var file = GetRandomCurrencyImagePath(); + IUserMessage msg; + var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(Gambling.Gambling.CurrencyName[0]); + + var msgToSend = $"Oh how Nice! **{imsg.Author.Username}** planted {(vowelFirst ? "an" : "a")} {Gambling.Gambling.CurrencyName}. Pick it using {NadekoBot.ModulePrefixes[typeof(Games).Name]}pick"; + if (file == null) + { + msg = await channel.SendMessageAsync(Gambling.Gambling.CurrencySign).ConfigureAwait(false); + } + else + { + msg = await channel.SendFileAsync(file, msgToSend).ConfigureAwait(false); + } + plantedFlowers.AddOrUpdate(channel.Id, new List() { msg }, (id, old) => { old.Add(msg); return old; }); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task GenCurrency(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var guildConfig = uow.GuildConfigs.For(channel.Id); + + var toAdd = new GCChannelId() { ChannelId = channel.Id }; + if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd)) + { + guildConfig.GenerateCurrencyChannelIds.Add(toAdd); + generationChannels.Add(channel.Id); + enabled = true; + } + else + { + guildConfig.GenerateCurrencyChannelIds.Remove(toAdd); + generationChannels.TryRemove(channel.Id); + enabled = false; + } + await uow.CompleteAsync(); + } + if (enabled) + { + await channel.SendMessageAsync("`Currency generation enabled on this channel.`").ConfigureAwait(false); + } + else + { + await channel.SendMessageAsync($"`Currency generation disabled on this channel.`").ConfigureAwait(false); + } + } + + private string GetRandomCurrencyImagePath() => + Directory.GetFiles("data/currency_images").OrderBy(s => rng.Next()).FirstOrDefault(); + + int GetRandomNumber() + { + using (var rg = RandomNumberGenerator.Create()) + { + byte[] rno = new byte[4]; + rg.GetBytes(rno); + int randomvalue = BitConverter.ToInt32(rno, 0); + return randomvalue; + } + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/PollCommands.cs b/src/NadekoBot/Modules/Games/Commands/PollCommands.cs new file mode 100644 index 00000000..66c72f02 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/PollCommands.cs @@ -0,0 +1,184 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + public static ConcurrentDictionary ActivePolls = new ConcurrentDictionary(); + + [NadekoCommand, Usage, Description, Aliases] + [RequirePermission(GuildPermission.ManageMessages)] + [RequireContext(ContextType.Guild)] + public Task Poll(IUserMessage umsg, [Remainder] string arg = null) + => InternalStartPoll(umsg, arg, isPublic: false); + + [NadekoCommand, Usage, Description, Aliases] + [RequirePermission(GuildPermission.ManageMessages)] + [RequireContext(ContextType.Guild)] + public Task PublicPoll(IUserMessage umsg, [Remainder] string arg = null) + => InternalStartPoll(umsg, arg, isPublic: true); + + private async Task InternalStartPoll(IUserMessage umsg, string arg, bool isPublic = false) + { + var channel = (ITextChannel)umsg.Channel; + + if (!(umsg.Author as IGuildUser).GuildPermissions.ManageChannels) + return; + if (string.IsNullOrWhiteSpace(arg) || !arg.Contains(";")) + return; + var data = arg.Split(';'); + if (data.Length < 3) + return; + + var poll = new Poll(umsg, data[0], data.Skip(1), isPublic: isPublic); + if (ActivePolls.TryAdd(channel.Guild, poll)) + { + await poll.StartPoll().ConfigureAwait(false); + } + else + await channel.SendMessageAsync("`Poll is already running on this server.`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequirePermission(GuildPermission.ManageMessages)] + [RequireContext(ContextType.Guild)] + public async Task Pollend(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + Poll poll; + ActivePolls.TryRemove(channel.Guild, out poll); + await poll.StopPoll().ConfigureAwait(false); + } + } + + public class Poll + { + private readonly IUserMessage originalMessage; + private readonly IGuild guild; + private readonly string[] answers; + private ConcurrentDictionary participants = new ConcurrentDictionary(); + private readonly string question; + private DateTime started; + private CancellationTokenSource pollCancellationSource = new CancellationTokenSource(); + private readonly bool isPublic; + + public Poll(IUserMessage umsg, string question, IEnumerable enumerable, bool isPublic = false) + { + this.originalMessage = umsg; + this.guild = ((ITextChannel)umsg.Channel).Guild; + this.question = question; + this.answers = enumerable as string[] ?? enumerable.ToArray(); + this.isPublic = isPublic; + } + + public async Task StartPoll() + { + started = DateTime.Now; + NadekoBot.Client.MessageReceived += Vote; + var msgToSend = $"📃**{originalMessage.Author.Username}** has created a poll which requires your attention:\n\n**{question}**\n"; + var num = 1; + msgToSend = answers.Aggregate(msgToSend, (current, answ) => current + $"`{num++}.` **{answ}**\n"); + if (!isPublic) + msgToSend += "\n**Private Message me with the corresponding number of the answer.**"; + else + msgToSend += "\n**Send a Message here with the corresponding number of the answer.**"; + await originalMessage.Channel.SendMessageAsync(msgToSend).ConfigureAwait(false); + } + + public async Task StopPoll() + { + NadekoBot.Client.MessageReceived -= Vote; + try + { + var results = participants.GroupBy(kvp => kvp.Value) + .ToDictionary(x => x.Key, x => x.Sum(kvp => 1)) + .OrderByDescending(kvp => kvp.Value); + + var totalVotesCast = results.Sum(kvp => kvp.Value); + if (totalVotesCast == 0) + { + await originalMessage.Channel.SendMessageAsync("📄 **No votes have been cast.**").ConfigureAwait(false); + return; + } + var closeMessage = $"--------------**POLL CLOSED**--------------\n" + + $"📄 , here are the results:\n"; + closeMessage = results.Aggregate(closeMessage, (current, kvp) => current + $"`{kvp.Key}.` **[{answers[kvp.Key - 1]}]**" + + $" has {kvp.Value} votes." + + $"({kvp.Value * 1.0f / totalVotesCast * 100}%)\n"); + + await originalMessage.Channel.SendMessageAsync($"📄 **Total votes cast**: {totalVotesCast}\n{closeMessage}").ConfigureAwait(false); + } + catch (Exception ex) + { + Console.WriteLine($"Error in poll game {ex}"); + } + } + + private Task Vote(IMessage imsg) + { + // has to be a user message + var msg = imsg as IUserMessage; + if (msg == null || msg.Author.IsBot) + return Task.CompletedTask; + + // has to be an integer + int vote; + if (!int.TryParse(imsg.Content, out vote)) + return Task.CompletedTask; + if (vote < 1 || vote > answers.Length) + return Task.CompletedTask; + + var t = Task.Run(async () => + { + try + { + IMessageChannel ch; + if (isPublic) + { + //if public, channel must be the same the poll started in + if (originalMessage.Channel.Id != imsg.Channel.Id) + return; + ch = imsg.Channel; + } + else + { + //if private, channel must be dm channel + if ((ch = msg.Channel as IDMChannel) == null) + return; + + // user must be a member of the guild this poll is in + var guildUsers = await guild.GetUsersAsync().ConfigureAwait(false); + if (!guildUsers.Any(u => u.Id == imsg.Author.Id)) + return; + } + + //user can vote only once + if (participants.TryAdd(msg.Author.Id, vote)) + { + if (!isPublic) + { + await ch.SendMessageAsync($"Thanks for voting **{msg.Author.Username}**.").ConfigureAwait(false); + } + else + { + var toDelete = await ch.SendMessageAsync($"{msg.Author.Mention} cast their vote.").ConfigureAwait(false); + await Task.Delay(5000); + await toDelete.DeleteAsync().ConfigureAwait(false); + } + } + } + catch { } + }); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs b/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs new file mode 100644 index 00000000..74f67dcd --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/SpeedTypingCommands.cs @@ -0,0 +1,261 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Games.Commands.Models; +using NadekoBot.Services; +using Newtonsoft.Json; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + public class TypingGame + { + public const float WORD_VALUE = 4.5f; + private readonly ITextChannel channel; + public string CurrentSentence; + public bool IsActive; + private readonly Stopwatch sw; + private readonly List finishedUserIds; + private Logger _log { get; } + + public TypingGame(ITextChannel channel) + { + _log = LogManager.GetCurrentClassLogger(); + this.channel = channel; + IsActive = false; + sw = new Stopwatch(); + finishedUserIds = new List(); + } + + public ITextChannel Channel { get; set; } + + public async Task Stop() + { + if (!IsActive) return false; + NadekoBot.Client.MessageReceived -= AnswerReceived; + finishedUserIds.Clear(); + IsActive = false; + sw.Stop(); + sw.Reset(); + try { await channel.SendMessageAsync("Typing contest stopped").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + return true; + } + + public async Task Start() + { + if (IsActive) return; // can't start running game + IsActive = true; + CurrentSentence = GetRandomSentence(); + var i = (int)(CurrentSentence.Length / WORD_VALUE * 1.7f); + try + { + await channel.SendMessageAsync($@":clock2: Next contest will last for {i} seconds. Type the bolded text as fast as you can.").ConfigureAwait(false); + + + var msg = await channel.SendMessageAsync("Starting new typing contest in **3**...").ConfigureAwait(false); + await Task.Delay(1000).ConfigureAwait(false); + try + { + await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **2**...").ConfigureAwait(false); + await Task.Delay(1000).ConfigureAwait(false); + await msg.ModifyAsync(m => m.Content = "Starting new typing contest in **1**...").ConfigureAwait(false); + await Task.Delay(1000).ConfigureAwait(false); + } + catch (Exception ex) { _log.Warn(ex); } + + await msg.ModifyAsync(m => m.Content = Format.Bold(Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions())).ConfigureAwait(false); + sw.Start(); + HandleAnswers(); + + while (i > 0) + { + await Task.Delay(1000).ConfigureAwait(false); + i--; + if (!IsActive) + return; + } + + } + catch { } + finally + { + await Stop().ConfigureAwait(false); + } + } + + public string GetRandomSentence() + { + if (SpeedTypingCommands.TypingArticles.Any()) + return SpeedTypingCommands.TypingArticles[new NadekoRandom().Next(0, SpeedTypingCommands.TypingArticles.Count)].Text; + else + return $"No typing articles found. Use {NadekoBot.ModulePrefixes[typeof(Games).Name]}typeadd command to add a new article for typing."; + + } + + private void HandleAnswers() + { + NadekoBot.Client.MessageReceived += AnswerReceived; + } + + private Task AnswerReceived(IMessage imsg) + { + if (imsg.Author.IsBot) + return Task.CompletedTask; + var msg = imsg as IUserMessage; + if (msg == null) + return Task.CompletedTask; + var t = Task.Run(async () => + { + try + { + if (channel == null || channel.Id != channel.Id) return; + + var guess = msg.Content; + + var distance = CurrentSentence.LevenshteinDistance(guess); + var decision = Judge(distance, guess.Length); + if (decision && !finishedUserIds.Contains(msg.Author.Id)) + { + finishedUserIds.Add(msg.Author.Id); + await channel.SendMessageAsync($"{msg.Author.Mention} finished in **{sw.Elapsed.Seconds}** seconds with { distance } errors, **{ CurrentSentence.Length / WORD_VALUE / sw.Elapsed.Seconds * 60 }** WPM!").ConfigureAwait(false); + if (finishedUserIds.Count % 4 == 0) + { + await channel.SendMessageAsync($":exclamation: `A lot of people finished, here is the text for those still typing:`\n\n**{Format.Sanitize(CurrentSentence.Replace(" ", " \x200B")).SanitizeMentions()}**").ConfigureAwait(false); + } + } + } + catch { } + }); + return Task.CompletedTask; + } + + private bool Judge(int errors, int textLength) => errors <= textLength / 25; + + } + + [Group] + public class SpeedTypingCommands + { + + public static List TypingArticles { get; } = new List(); + + const string typingArticlesPath = "data/typing_articles.json"; + + static SpeedTypingCommands() + { + try { TypingArticles = JsonConvert.DeserializeObject>(File.ReadAllText(typingArticlesPath)); } catch { } + } + public static ConcurrentDictionary RunningContests; + + public SpeedTypingCommands() + { + RunningContests = new ConcurrentDictionary(); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task TypeStart(IUserMessage msg) + { + var channel = (ITextChannel)msg.Channel; + + var game = RunningContests.GetOrAdd(channel.Guild.Id, id => new TypingGame(channel)); + + if (game.IsActive) + { + await channel.SendMessageAsync( + $"Contest already running in " + + $"{game.Channel.Mention} channel.") + .ConfigureAwait(false); + } + else + { + await game.Start().ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task TypeStop(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + TypingGame game; + if (RunningContests.TryRemove(channel.Guild.Id, out game)) + { + await game.Stop().ConfigureAwait(false); + return; + } + await channel.SendMessageAsync("No contest to stop on this channel.").ConfigureAwait(false); + } + + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Typeadd(IUserMessage imsg, [Remainder] string text) + { + var channel = (ITextChannel)imsg.Channel; + + TypingArticles.Add(new TypingArticle + { + Title = $"Text added on {DateTime.UtcNow} by {imsg.Author}", + Text = text.SanitizeMentions(), + }); + + File.WriteAllText(typingArticlesPath, JsonConvert.SerializeObject(TypingArticles)); + + await channel.SendMessageAsync("Added new article for typing game.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Typelist(IUserMessage imsg, int page = 1) + { + var channel = (ITextChannel)imsg.Channel; + + if (page < 1) + return; + + var articles = TypingArticles.Skip((page - 1) * 15).Take(15); + + if (!articles.Any()) + { + await channel.SendMessageAsync($"{imsg.Author.Mention} `No articles found on that page.`").ConfigureAwait(false); + return; + } + var i = (page - 1) * 15; + await channel.SendMessageAsync(String.Join("\n", articles.Select(a => $"`#{++i}` - {a.Text.TrimTo(50)}"))) + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Typedel(IUserMessage imsg, int index) + { + var channel = (ITextChannel)imsg.Channel; + + index -= 1; + if (index < 0 || index >= TypingArticles.Count) + return; + + var removed = TypingArticles[index]; + TypingArticles.RemoveAt(index); + + File.WriteAllText(typingArticlesPath, JsonConvert.SerializeObject(TypingArticles)); + + await channel.SendMessageAsync($"`Removed typing article:` #{index + 1} - {removed.Text.TrimTo(50)}") + .ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs new file mode 100644 index 00000000..a52b8122 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -0,0 +1,178 @@ +using Discord; +using Discord.Net; +using NadekoBot.Extensions; +using NLog; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Games.Trivia +{ + public class TriviaGame + { + private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1); + private Logger _log { get; } + + public IGuild guild { get; } + public ITextChannel channel { get; } + + private int QuestionDurationMiliseconds { get; } = 30000; + private int HintTimeoutMiliseconds { get; } = 6000; + public bool ShowHints { get; set; } = true; + private CancellationTokenSource triviaCancelSource { get; set; } + + public TriviaQuestion CurrentQuestion { get; private set; } + public HashSet oldQuestions { get; } = new HashSet(); + + public ConcurrentDictionary Users { get; } = new ConcurrentDictionary(); + + public bool GameActive { get; private set; } = false; + public bool ShouldStopGame { get; private set; } + + public int WinRequirement { get; } = 10; + + public TriviaGame(IGuild guild, ITextChannel channel, bool showHints, int winReq = 10) + { + _log = LogManager.GetCurrentClassLogger(); + ShowHints = showHints; + this.guild = guild; + this.channel = channel; + WinRequirement = winReq; + Task.Run(async () => { try { await StartGame().ConfigureAwait(false); } catch { } }); + } + + private async Task StartGame() + { + while (!ShouldStopGame) + { + // reset the cancellation source + triviaCancelSource = new CancellationTokenSource(); + var token = triviaCancelSource.Token; + // load question + CurrentQuestion = TriviaQuestionPool.Instance.GetRandomQuestion(oldQuestions); + if (CurrentQuestion == null) + { + try { await channel.SendMessageAsync($":exclamation: Failed loading a trivia question").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + await End().ConfigureAwait(false); + return; + } + oldQuestions.Add(CurrentQuestion); //add it to exclusion list so it doesn't show up again + //sendquestion + try { await channel.SendMessageAsync($":question: **{CurrentQuestion.Question}**").ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + break; + } + catch (Exception ex) { _log.Warn(ex); } + + //receive messages + NadekoBot.Client.MessageReceived += PotentialGuess; + + //allow people to guess + GameActive = true; + + try + { + //hint + await Task.Delay(HintTimeoutMiliseconds, token).ConfigureAwait(false); + if (ShowHints) + try { await channel.SendMessageAsync($":exclamation:**Hint:** {CurrentQuestion.GetHint()}").ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound || ex.StatusCode == System.Net.HttpStatusCode.Forbidden) + { + break; + } + catch (Exception ex) { _log.Warn(ex); } + + //timeout + await Task.Delay(QuestionDurationMiliseconds - HintTimeoutMiliseconds, token).ConfigureAwait(false); + + } + catch (TaskCanceledException) { } //means someone guessed the answer + GameActive = false; + if (!triviaCancelSource.IsCancellationRequested) + try { await channel.SendMessageAsync($":clock2: :question: **Time's up!** The correct answer was **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + NadekoBot.Client.MessageReceived -= PotentialGuess; + // load next question if game is still running + await Task.Delay(2000).ConfigureAwait(false); + } + try { NadekoBot.Client.MessageReceived -= PotentialGuess; } catch { } + GameActive = false; + await End().ConfigureAwait(false); + } + + private async Task End() + { + ShouldStopGame = true; + TriviaGame throwaway; + Games.TriviaCommands.RunningTrivias.TryRemove(channel.Guild.Id, out throwaway); + try { await channel.SendMessageAsync("**Trivia game ended**\n" + GetLeaderboard()).ConfigureAwait(false); } catch { } + } + + public async Task StopGame() + { + if (!ShouldStopGame) + try { await channel.SendMessageAsync(":exclamation: Trivia will stop after this question.").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + ShouldStopGame = true; + } + + private Task PotentialGuess(IMessage imsg) + { + if (imsg.Author.IsBot) + return Task.CompletedTask; + var umsg = imsg as IUserMessage; + if (umsg == null) + return Task.CompletedTask; + var t = Task.Run(async () => + { + try + { + if (!(umsg.Channel is IGuildChannel && umsg.Channel is ITextChannel)) return; + if ((umsg.Channel as ITextChannel).Guild != guild) return; + if (umsg.Author.Id == NadekoBot.Client.GetCurrentUser().Id) return; + + var guildUser = umsg.Author as IGuildUser; + + var guess = false; + await _guessLock.WaitAsync().ConfigureAwait(false); + try + { + if (GameActive && CurrentQuestion.IsAnswerCorrect(umsg.Content) && !triviaCancelSource.IsCancellationRequested) + { + Users.AddOrUpdate(guildUser, 1, (gu, old) => ++old); + guess = true; + } + } + finally { _guessLock.Release(); } + if (!guess) return; + triviaCancelSource.Cancel(); + try { await channel.SendMessageAsync($"☑️ {guildUser.Mention} guessed it! The answer was: **{CurrentQuestion.Answer}**").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + if (Users[guildUser] != WinRequirement) return; + ShouldStopGame = true; + await channel.SendMessageAsync($":exclamation: We have a winner! It's {guildUser.Mention}.").ConfigureAwait(false); + } + catch (Exception ex) { _log.Warn(ex); } + }); + return Task.CompletedTask; + } + + public string GetLeaderboard() + { + if (Users.Count == 0) + return ""; + + var sb = new StringBuilder(); + sb.Append("**Leaderboard:**\n-----------\n"); + + foreach (var kvp in Users.OrderByDescending(kvp => kvp.Value)) + { + sb.AppendLine($"**{kvp.Key.Username}** has {kvp.Value} points".ToString().SnPl(kvp.Value)); + } + + return sb.ToString(); + } + } +} diff --git a/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs similarity index 78% rename from NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs rename to src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs index 9bfa2da4..84f332bb 100644 --- a/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs @@ -1,10 +1,11 @@ using NadekoBot.Extensions; using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; // THANKS @ShoMinamimoto for suggestions and coding help -namespace NadekoBot.Modules.Games.Commands.Trivia +namespace NadekoBot.Modules.Games.Trivia { public class TriviaQuestion { @@ -28,7 +29,7 @@ namespace NadekoBot.Modules.Games.Commands.Trivia this.Category = c; } - public string GetHint() => Answer.Scramble(); + public string GetHint() => Scramble(Answer); public bool IsAnswerCorrect(string guess) { @@ -80,5 +81,27 @@ namespace NadekoBot.Modules.Games.Commands.Trivia public override string ToString() => "Question: **" + this.Question + "?**"; + + private static string Scramble(string word) + { + var letters = word.ToArray(); + var count = 0; + for (var i = 0; i < letters.Length; i++) + { + if (letters[i] == ' ') + continue; + + count++; + if (count <= letters.Length / 5) + continue; + + if (count % 3 == 0) + continue; + + if (letters[i] != ' ') + letters[i] = '_'; + } + return "`" + string.Join(" ", letters) + "`"; + } } } diff --git a/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs similarity index 59% rename from NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs rename to src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs index 3aa09a56..3afa8a9c 100644 --- a/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs +++ b/src/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs @@ -1,18 +1,20 @@ -using Newtonsoft.Json.Linq; +using NadekoBot.Extensions; +using NadekoBot.Services; +using Newtonsoft.Json.Linq; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; -namespace NadekoBot.Modules.Games.Commands.Trivia +namespace NadekoBot.Modules.Games.Trivia { public class TriviaQuestionPool { public static TriviaQuestionPool Instance { get; } = new TriviaQuestionPool(); + public ConcurrentHashSet pool = new ConcurrentHashSet(); - public HashSet pool = new HashSet(); - - private Random rng { get; } = new Random(); + private Random rng { get; } = new NadekoRandom(); static TriviaQuestionPool() { } @@ -28,17 +30,17 @@ namespace NadekoBot.Modules.Games.Commands.Trivia return list[rand]; } - internal void Reload() + public void Reload() { var arr = JArray.Parse(File.ReadAllText("data/questions.json")); foreach (var item in arr) { - var tq = new TriviaQuestion(item["Question"].ToString(), item["Answer"].ToString(), item["Category"]?.ToString()); + var tq = new TriviaQuestion(item["Question"].ToString().SanitizeMentions(), item["Answer"].ToString().SanitizeMentions(), item["Category"]?.ToString()); pool.Add(tq); } - var r = new Random(); - pool = new HashSet(pool.OrderBy(x => r.Next())); + var r = new NadekoRandom(); + pool = new ConcurrentHashSet(pool.OrderBy(x => r.Next())); } } } diff --git a/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs b/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs new file mode 100644 index 00000000..56fd3fc1 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Commands/TriviaCommands.cs @@ -0,0 +1,76 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Modules.Games.Trivia; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +//todo Rewrite? Fix trivia not stopping bug +namespace NadekoBot.Modules.Games +{ + public partial class Games + { + [Group] + public class TriviaCommands + { + public static ConcurrentDictionary RunningTrivias = new ConcurrentDictionary(); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Trivia(IUserMessage umsg, params string[] args) + { + var channel = (ITextChannel)umsg.Channel; + + TriviaGame trivia; + if (!RunningTrivias.TryGetValue(channel.Guild.Id, out trivia)) + { + var showHints = !args.Contains("nohint"); + var number = args.Select(s => + { + int num; + return new Tuple(int.TryParse(s, out num), num); + }).Where(t => t.Item1).Select(t => t.Item2).FirstOrDefault(); + if (number < 0) + return; + var triviaGame = new TriviaGame(channel.Guild, (ITextChannel)umsg.Channel, showHints, number == 0 ? 10 : number); + if (RunningTrivias.TryAdd(channel.Guild.Id, triviaGame)) + await channel.SendMessageAsync($"**Trivia game started! {triviaGame.WinRequirement} points needed to win.**").ConfigureAwait(false); + else + await triviaGame.StopGame().ConfigureAwait(false); + } + else + await channel.SendMessageAsync("Trivia game is already running on this server.\n" + trivia.CurrentQuestion).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Tl(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + TriviaGame trivia; + if (RunningTrivias.TryGetValue(channel.Guild.Id, out trivia)) + await channel.SendMessageAsync(trivia.GetLeaderboard()).ConfigureAwait(false); + else + await channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Tq(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + TriviaGame trivia; + if (RunningTrivias.TryRemove(channel.Guild.Id, out trivia)) + { + await trivia.StopGame().ConfigureAwait(false); + } + else + await channel.SendMessageAsync("No trivia is running on this server.").ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Games/Games.cs b/src/NadekoBot/Modules/Games/Games.cs new file mode 100644 index 00000000..115fdb94 --- /dev/null +++ b/src/NadekoBot/Modules/Games/Games.cs @@ -0,0 +1,120 @@ +using Discord.Commands; +using Discord; +using NadekoBot.Services; +using System.Threading.Tasks; +using NadekoBot.Attributes; +using System; +using System.Linq; +using System.Collections.Generic; +using NadekoBot.Extensions; + +namespace NadekoBot.Modules.Games +{ + [NadekoModule("Games", ">")] + public partial class Games : DiscordModule + { + private IEnumerable _8BallResponses { + get { + using (var uow = DbHandler.UnitOfWork()) + { + return uow.BotConfig.GetOrCreate().EightBallResponses.Select(ebr => ebr.Text); + } + } + } + public Games(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Choose(IUserMessage umsg, [Remainder] string list = null) + { + var channel = (ITextChannel)umsg.Channel; + if (string.IsNullOrWhiteSpace(list)) + return; + var listArr = list.Split(';'); + if (listArr.Count() < 2) + return; + var rng = new NadekoRandom(); + await channel.SendMessageAsync(listArr[rng.Next(0, listArr.Length)]).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task _8Ball(IUserMessage umsg, [Remainder] string question = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(question)) + return; + var rng = new NadekoRandom(); + await channel.SendMessageAsync($@"❓ `Question` __**{question}**__ +🎱 `8Ball Answers` __**{_8BallResponses.Shuffle().FirstOrDefault()}**__").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Rps(IUserMessage umsg, string input) + { + var channel = (ITextChannel)umsg.Channel; + + Func GetRPSPick = (p) => + { + if (p == 0) + return "🚀"; + else if (p == 1) + return "📎"; + else + return "✂️"; + }; + + int pick; + switch (input) + { + case "r": + case "rock": + case "rocket": + pick = 0; + break; + case "p": + case "paper": + case "paperclip": + pick = 1; + break; + case "scissors": + case "s": + pick = 2; + break; + default: + return; + } + var nadekoPick = new NadekoRandom().Next(0, 3); + var msg = ""; + if (pick == nadekoPick) + msg = $"It's a draw! Both picked {GetRPSPick(pick)}"; + else if ((pick == 0 && nadekoPick == 1) || + (pick == 1 && nadekoPick == 2) || + (pick == 2 && nadekoPick == 0)) + msg = $"{NadekoBot.Client.GetCurrentUser().Mention} won! {GetRPSPick(nadekoPick)} beats {GetRPSPick(pick)}"; + else + msg = $"{umsg.Author.Mention} won! {GetRPSPick(pick)} beats {GetRPSPick(nadekoPick)}"; + + await channel.SendMessageAsync(msg).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Linux(IUserMessage umsg, string guhnoo, string loonix) + { + var channel = (ITextChannel)umsg.Channel; + + await channel.SendMessageAsync( +$@"I'd just like to interject for moment. What you're refering to as {loonix}, is in fact, {guhnoo}/{loonix}, or as I've recently taken to calling it, {guhnoo} plus {loonix}. {loonix} is not an operating system unto itself, but rather another free component of a fully functioning {guhnoo} system made useful by the {guhnoo} corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. + +Many computer users run a modified version of the {guhnoo} system every day, without realizing it. Through a peculiar turn of events, the version of {guhnoo} which is widely used today is often called {loonix}, and many of its users are not aware that it is basically the {guhnoo} system, developed by the {guhnoo} Project. + +There really is a {loonix}, and these people are using it, but it is just a part of the system they use. {loonix} is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. {loonix} is normally used in combination with the {guhnoo} operating system: the whole system is basically {guhnoo} with {loonix} added, or {guhnoo}/{loonix}. All the so-called {loonix} distributions are really distributions of {guhnoo}/{loonix}." + ).ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/Modules/Help/Help.cs b/src/NadekoBot/Modules/Help/Help.cs new file mode 100644 index 00000000..b0e1338b --- /dev/null +++ b/src/NadekoBot/Modules/Help/Help.cs @@ -0,0 +1,184 @@ +using Discord.Commands; +using NadekoBot.Extensions; +using System.Linq; +using Discord; +using NadekoBot.Services; +using System.Threading.Tasks; +using NadekoBot.Attributes; +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; + +namespace NadekoBot.Modules.Help +{ + [NadekoModule("Help", "-")] + public partial class Help : DiscordModule + { + private static string helpString { get; } + public static string HelpString => String.Format(helpString, NadekoBot.Credentials.ClientId, NadekoBot.ModulePrefixes[typeof(Help).Name]); + + public static string DMHelpString { get; } + + static Help() + { + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.BotConfig.GetOrCreate(); + helpString = config.HelpString; + DMHelpString = config.DMHelpString; + } + } + + public Help(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task Modules(IUserMessage umsg) + { + + await umsg.Channel.SendMessageAsync("📜 **List of modules:** ```css\n• " + string.Join("\n• ", _commands.Modules.Select(m => m.Name)) + $"\n``` ℹ️ **Type** `-commands module_name` **to get a list of commands in that module.** ***e.g.*** `-commands games`") + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task Commands(IUserMessage umsg, [Remainder] string module = null) + { + var channel = umsg.Channel; + + module = module?.Trim().ToUpperInvariant(); + if (string.IsNullOrWhiteSpace(module)) + return; + var cmds = _commands.Commands.Where(c => c.Module.Name.ToUpperInvariant().StartsWith(module)) + .OrderBy(c => c.Text) + .Distinct(new CommandTextEqualityComparer()) + .AsEnumerable(); + + var cmdsArray = cmds as Command[] ?? cmds.ToArray(); + if (!cmdsArray.Any()) + { + await channel.SendMessageAsync("🚫 **That module does not exist.**").ConfigureAwait(false); + return; + } + if (module != "customreactions" && module != "conversations") + { + await channel.SendTableAsync("📃 **List Of Commands:**\n", cmdsArray, el => $"{el.Text,-15} {"["+el.Aliases.Skip(1).FirstOrDefault()+"]",-8}").ConfigureAwait(false); + } + else + { + await channel.SendMessageAsync("📃 **List Of Commands:**\n• " + string.Join("\n• ", cmdsArray.Select(c => $"{c.Text}"))); + } + await channel.SendMessageAsync($"ℹ️ **Type** `\"{NadekoBot.ModulePrefixes[typeof(Help).Name]}h CommandName\"` **to see the help for that specified command.** ***e.g.*** `-h >8ball`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task H(IUserMessage umsg, [Remainder] string comToFind = null) + { + var channel = umsg.Channel; + + comToFind = comToFind?.ToLowerInvariant(); + if (string.IsNullOrWhiteSpace(comToFind)) + { + IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)umsg.Author).CreateDMChannelAsync() : channel; + await ch.SendMessageAsync(HelpString).ConfigureAwait(false); + return; + } + var com = _commands.Commands.FirstOrDefault(c => c.Text.ToLowerInvariant() == comToFind || c.Aliases.Select(a=>a.ToLowerInvariant()).Contains(comToFind)); + + if (com == null) + { + await channel.SendMessageAsync("🔍 **I can't find that command.**"); + return; + } + var str = $"**__Help for:__ `{com.Text}`**"; + var alias = com.Aliases.Skip(1).FirstOrDefault(); + if (alias != null) + str += $" / `{alias}`"; + if (com != null) + await channel.SendMessageAsync(str + $@"{Environment.NewLine}**Desc:** {string.Format(com.Summary, com.Module.Prefix)} {GetCommandRequirements(com)} +**Usage:** {string.Format(com.Remarks, com.Module.Prefix)}").ConfigureAwait(false); + } + + private string GetCommandRequirements(Command cmd) + { + return String.Join(" ", cmd.Source.CustomAttributes + .Where(ca => ca.AttributeType == typeof(OwnerOnlyAttribute) || ca.AttributeType == typeof(RequirePermissionAttribute)) + .Select(ca => + { + if (ca.AttributeType == typeof(OwnerOnlyAttribute)) + return "**Bot Owner only.**"; + else if (ca.AttributeType == typeof(RequirePermissionAttribute)) + return $"**Requires {(GuildPermission)ca.ConstructorArguments.FirstOrDefault().Value} server permission.**".Replace("Guild", "Server"); + else + return $"**Requires {(GuildPermission)ca.ConstructorArguments.FirstOrDefault().Value} channel permission.**".Replace("Guild", "Server"); + })); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public Task Hgit(IUserMessage umsg) + { + var helpstr = new StringBuilder(); + helpstr.AppendLine("You can support the project on patreon: or paypal: \n"); + helpstr.AppendLine("##Table Of Contents"); + helpstr.AppendLine(string.Join("\n", NadekoBot.CommandService.Modules.Where(m => m.Name.ToLowerInvariant() != "help").OrderBy(m => m.Name).Prepend(NadekoBot.CommandService.Modules.FirstOrDefault(m=>m.Name.ToLowerInvariant()=="help")).Select(m => $"- [{m.Name}](#{m.Name.ToLowerInvariant()})"))); + helpstr.AppendLine(); + string lastModule = null; + foreach (var com in _commands.Commands.OrderBy(com=>com.Module.Name).GroupBy(c=>c.Text).Select(g=>g.First())) + { + if (com.Module.Name != lastModule) + { + if (lastModule != null) + { + helpstr.AppendLine(); + helpstr.AppendLine("###### [Back to TOC](#table-of-contents)"); + } + helpstr.AppendLine(); + helpstr.AppendLine("### " + com.Module.Name + " "); + helpstr.AppendLine("Command and aliases | Description | Usage"); + helpstr.AppendLine("----------------|--------------|-------"); + lastModule = com.Module.Name; + } + helpstr.AppendLine($"`{com.Text}` {string.Join(" ", com.Aliases.Skip(1).Select(a=>"`"+a+"`"))} | {string.Format(com.Summary, com.Module.Prefix)} {GetCommandRequirements(com)} | {string.Format(com.Remarks, com.Module.Prefix)}"); + } + helpstr = helpstr.Replace(NadekoBot.Client.GetCurrentUser().Username , "@BotName"); + File.WriteAllText("../../docs/Commands List.md", helpstr.ToString()); + return Task.CompletedTask; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Guide(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + await channel.SendMessageAsync( +@"**LIST OF COMMANDS**: +**Hosting Guides and docs can be found here**: ").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Donate(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + await channel.SendMessageAsync( +$@"You can support the NadekoBot project on patreon. or +You can send donations to `nadekodiscordbot@gmail.com` +Don't forget to leave your discord name or id in the message. + +**Thank you** ♥️").ConfigureAwait(false); + } + } + + public class CommandTextEqualityComparer : IEqualityComparer + { + public bool Equals(Command x, Command y) => x.Text == y.Text; + + public int GetHashCode(Command obj) => obj.Text.GetHashCode(); + + } +} diff --git a/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs b/src/NadekoBot/Modules/Music/Classes/Exceptions.cs similarity index 55% rename from NadekoBot/Modules/Music/Classes/PlaylistFullException.cs rename to src/NadekoBot/Modules/Music/Classes/Exceptions.cs index 15541d42..3dd355f9 100644 --- a/NadekoBot/Modules/Music/Classes/PlaylistFullException.cs +++ b/src/NadekoBot/Modules/Music/Classes/Exceptions.cs @@ -9,4 +9,12 @@ namespace NadekoBot.Modules.Music.Classes } public PlaylistFullException() : base("Queue is full.") { } } + + class SongNotFoundException : Exception + { + public SongNotFoundException(string message) : base(message) + { + } + public SongNotFoundException() : base("Song is not found.") { } + } } diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs similarity index 62% rename from NadekoBot/Modules/Music/Classes/MusicControls.cs rename to src/NadekoBot/Modules/Music/Classes/MusicControls.cs index e13239cf..6cc3560d 100644 --- a/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/src/NadekoBot/Modules/Music/Classes/MusicControls.cs @@ -44,7 +44,7 @@ namespace NadekoBot.Modules.Music.Classes public event EventHandler OnCompleted = delegate { }; public event EventHandler OnStarted = delegate { }; - public Channel PlaybackVoiceChannel { get; private set; } + public IVoiceChannel PlaybackVoiceChannel { get; private set; } private bool Destroyed { get; set; } = false; public bool RepeatSong { get; private set; } = false; @@ -54,12 +54,10 @@ namespace NadekoBot.Modules.Music.Classes private ConcurrentQueue actionQueue { get; set; } = new ConcurrentQueue(); - public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) + public MusicPlayer(IVoiceChannel startingVoiceChannel, float? defaultVolume) { if (startingVoiceChannel == null) throw new ArgumentNullException(nameof(startingVoiceChannel)); - if (startingVoiceChannel.Type != ChannelType.Voice) - throw new ArgumentException("Channel must be of type voice"); Volume = defaultVolume ?? 1.0f; PlaybackVoiceChannel = startingVoiceChannel; @@ -95,53 +93,54 @@ namespace NadekoBot.Modules.Music.Classes var t = new Thread(new ThreadStart(async () => { - try + while (!Destroyed) { - while (!Destroyed) + try { - try + if (audioClient?.ConnectionState != ConnectionState.Connected) { - if (audioClient?.State != ConnectionState.Connected) - { - audioClient = await PlaybackVoiceChannel.JoinAudio(); - continue; - } - - CurrentSong = GetNextSong(); - RemoveSongAt(0); - - if (CurrentSong == null) - continue; - - - OnStarted(this, CurrentSong); - await CurrentSong.Play(audioClient, cancelToken); - - OnCompleted(this, CurrentSong); - - if (RepeatPlaylist) - AddSong(CurrentSong, CurrentSong.QueuerName); - - if (RepeatSong) - AddSong(CurrentSong, 0); - - } - finally - { - if (!cancelToken.IsCancellationRequested) - { - SongCancelSource.Cancel(); - } - SongCancelSource = new CancellationTokenSource(); - cancelToken = SongCancelSource.Token; - CurrentSong = null; - await Task.Delay(300).ConfigureAwait(false); + if (audioClient != null) + try { await audioClient.DisconnectAsync().ConfigureAwait(false); } catch { } + audioClient = await PlaybackVoiceChannel.ConnectAsync().ConfigureAwait(false); + continue; } + + CurrentSong = GetNextSong(); + RemoveSongAt(0); + + if (CurrentSong == null) + continue; + + + OnStarted(this, CurrentSong); + await CurrentSong.Play(audioClient, cancelToken); + + OnCompleted(this, CurrentSong); + + if (RepeatPlaylist) + AddSong(CurrentSong, CurrentSong.QueuerName); + + if (RepeatSong) + AddSong(CurrentSong, 0); + + } + catch (OperationCanceledException) { } + catch (Exception ex) + { + Console.WriteLine("Music thread almost crashed."); + Console.WriteLine(ex); + } + finally + { + if (!cancelToken.IsCancellationRequested) + { + SongCancelSource.Cancel(); + } + SongCancelSource = new CancellationTokenSource(); + cancelToken = SongCancelSource.Token; + CurrentSong = null; + await Task.Delay(300).ConfigureAwait(false); } - } - catch (Exception ex) { - Console.WriteLine("Music thread crashed."); - Console.WriteLine(ex); } })); @@ -189,7 +188,9 @@ namespace NadekoBot.Modules.Music.Classes { actionQueue.Enqueue(() => { - playlist.Shuffle(); + var oldPlaylist = playlist.ToArray(); + playlist.Clear(); + playlist.AddRange(oldPlaylist.Shuffle()); }); } @@ -236,7 +237,7 @@ namespace NadekoBot.Modules.Music.Classes }); } - internal void ClearQueue() + public void ClearQueue() { actionQueue.Enqueue(() => { @@ -244,35 +245,61 @@ namespace NadekoBot.Modules.Music.Classes }); } + public async Task UpdateSongDurationsAsync() + { + var curSong = CurrentSong; + var toUpdate = playlist.Where(s => s.SongInfo.ProviderType == MusicType.Normal && + s.TotalLength == TimeSpan.Zero); + if (curSong != null) + toUpdate = toUpdate.Append(curSong); + var ids = toUpdate.Select(s => s.SongInfo.Query.Substring(s.SongInfo.Query.LastIndexOf("?v=") + 3)) + .Distinct(); + + var durations = await NadekoBot.Google.GetVideoDurationsAsync(ids); + + toUpdate.ForEach(s => + { + foreach (var kvp in durations) + { + if (s.SongInfo.Query.EndsWith(kvp.Key)) + { + s.TotalLength = kvp.Value; + return; + } + } + }); + } + public void Destroy() { - actionQueue.Enqueue(() => + actionQueue.Enqueue(async () => { RepeatPlaylist = false; RepeatSong = false; Destroyed = true; playlist.Clear(); + + try { await audioClient.DisconnectAsync(); } catch { } if (!SongCancelSource.IsCancellationRequested) SongCancelSource.Cancel(); - audioClient.Disconnect(); }); } - internal Task MoveToVoiceChannel(Channel voiceChannel) + public Task MoveToVoiceChannel(IVoiceChannel voiceChannel) { - if (audioClient?.State != ConnectionState.Connected) + if (audioClient?.ConnectionState != ConnectionState.Connected) throw new InvalidOperationException("Can't move while bot is not connected to voice channel."); PlaybackVoiceChannel = voiceChannel; - return PlaybackVoiceChannel.JoinAudio(); + return PlaybackVoiceChannel.ConnectAsync(); } - internal bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong; + public bool ToggleRepeatSong() => this.RepeatSong = !this.RepeatSong; - internal bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist; + public bool ToggleRepeatPlaylist() => this.RepeatPlaylist = !this.RepeatPlaylist; - internal bool ToggleAutoplay() => this.Autoplay = !this.Autoplay; + public bool ToggleAutoplay() => this.Autoplay = !this.Autoplay; - internal void ThrowIfQueueFull() + public void ThrowIfQueueFull() { if (MaxQueueSize == 0) return; diff --git a/NadekoBot/Modules/Music/Classes/Song.cs b/src/NadekoBot/Modules/Music/Classes/Song.cs similarity index 69% rename from NadekoBot/Modules/Music/Classes/Song.cs rename to src/NadekoBot/Modules/Music/Classes/Song.cs index 0b0a479a..73a39ced 100644 --- a/NadekoBot/Modules/Music/Classes/Song.cs +++ b/src/NadekoBot/Modules/Music/Classes/Song.cs @@ -1,11 +1,12 @@ using Discord.Audio; -using NadekoBot.Classes; using NadekoBot.Extensions; +using NLog; using System; using System.Diagnostics; using System.Diagnostics.Contracts; using System.IO; using System.Linq; +using System.Net.Http; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -15,18 +16,18 @@ namespace NadekoBot.Modules.Music.Classes { public class SongInfo { - public string Provider { get; internal set; } - public MusicType ProviderType { get; internal set; } + public string Provider { get; set; } + public MusicType ProviderType { get; set; } /// /// Will be set only if the providertype is normal /// - public string Query { get; internal set; } - public string Title { get; internal set; } - public string Uri { get; internal set; } + public string Query { get; set; } + public string Title { get; set; } + public string Uri { get; set; } } public class Song { - public StreamState State { get; internal set; } + public StreamState State { get; set; } public string PrettyName => $"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}` `by {QueuerName}`"; public SongInfo SongInfo { get; } @@ -37,25 +38,41 @@ namespace NadekoBot.Modules.Music.Classes public string PrettyCurrentTime() { var time = TimeSpan.FromSeconds(bytesSent / 3840 / 50); - return $"【{(int)time.TotalMinutes}m {time.Seconds}s】"; + var str = $"【{(int)time.TotalMinutes}m {time.Seconds}s】**/** "; + if (TotalLength == TimeSpan.Zero) + str += "**?**"; + else if (TotalLength == TimeSpan.MaxValue) + str += "**∞**"; + else + str += $"【{(int)TotalLength.TotalMinutes}m {TotalLength.Seconds}s】"; + return str; } + const int milliseconds = 20; + const int samplesPerFrame = (48000 / 1000) * milliseconds; + const int frameBytes = 3840; //16-bit, 2 channels + private ulong bytesSent { get; set; } = 0; public bool PrintStatusMessage { get; set; } = true; private int skipTo = 0; + private Logger _log; + public int SkipTo { - get { return SkipTo; } + get { return skipTo; } set { skipTo = value; bytesSent = (ulong)skipTo * 3840 * 50; } } + public TimeSpan TotalLength { get; set; } = TimeSpan.Zero; + public Song(SongInfo songInfo) { this.SongInfo = songInfo; + this._log = LogManager.GetCurrentClassLogger(); } public Song Clone() @@ -72,14 +89,12 @@ namespace NadekoBot.Modules.Music.Classes return this; } - internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) + public async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) { - var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString()); + var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString()); - SongBuffer sb = new SongBuffer(filename, SongInfo, skipTo); - var bufferTask = sb.BufferSong(cancelToken).ConfigureAwait(false); - - var inStream = new FileStream(sb.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); ; + SongBuffer inStream = new SongBuffer(MusicPlayer, filename, SongInfo, skipTo, frameBytes * 100); + var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false); bytesSent = 0; @@ -87,55 +102,80 @@ namespace NadekoBot.Modules.Music.Classes { var attempt = 0; - var prebufferingTask = CheckPrebufferingAsync(inStream, sb, cancelToken); + var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 1.MiB()); //Fast connection can do this easy + var finished = false; + var count = 0; var sw = new Stopwatch(); + var slowconnection = false; sw.Start(); - var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken)); - if (t != prebufferingTask) + while (!finished) { - Console.WriteLine("Prebuffering timed out or canceled. Cannot get any data from the stream."); - return; - } - else if(prebufferingTask.IsCanceled) - { - Console.WriteLine("Prebuffering timed out. Cannot get any data from the stream."); - return; + var t = await Task.WhenAny(prebufferingTask, Task.Delay(2000, cancelToken)); + if (t != prebufferingTask) + { + count++; + if (count == 10) + { + slowconnection = true; + prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken, 20.MiB()); + _log.Warn("Slow connection buffering more to ensure no disruption, consider hosting in cloud"); + continue; + } + + if (inStream.BufferingCompleted && count == 1) + { + _log.Debug("Prebuffering canceled. Cannot get any data from the stream."); + return; + } + else + { + continue; + } + } + else if (prebufferingTask.IsCanceled) + { + _log.Debug("Prebuffering canceled. Cannot get any data from the stream."); + return; + } + finished = true; } sw.Stop(); - Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed); + _log.Debug("Prebuffering successfully completed in "+ sw.Elapsed); - const int blockSize = 3840; - byte[] buffer = new byte[blockSize]; + var outStream = voiceClient.CreatePCMStream(960); + + int nextTime = Environment.TickCount + milliseconds; + + byte[] buffer = new byte[frameBytes]; while (!cancelToken.IsCancellationRequested) { //Console.WriteLine($"Read: {songBuffer.ReadPosition}\nWrite: {songBuffer.WritePosition}\nContentLength:{songBuffer.ContentLength}\n---------"); - var read = inStream.Read(buffer, 0, buffer.Length); + var read = await inStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); //await inStream.CopyToAsync(voiceClient.OutputStream); + if(read < frameBytes) + _log.Debug("read {0}", read); unchecked { bytesSent += (ulong)read; } - if (read < blockSize) + if (read < frameBytes) { - if (sb.IsNextFileReady()) - { - inStream.Dispose(); - inStream = new FileStream(sb.GetNextFile(), FileMode.Open, FileAccess.Read, FileShare.Write); - read += inStream.Read(buffer, read, buffer.Length - read); - attempt = 0; - } if (read == 0) { - if (sb.BufferingCompleted) + if (inStream.BufferingCompleted) break; if (attempt++ == 20) { - voiceClient.Wait(); MusicPlayer.SongCancelSource.Cancel(); break; } + if (slowconnection) + { + _log.Warn("Slow connection has disrupted music, waiting a bit for buffer"); + await Task.Delay(1000, cancelToken).ConfigureAwait(false); + } else - await Task.Delay(100, cancelToken).ConfigureAwait(false); + await Task.Delay(100, cancelToken).ConfigureAwait(false); } else attempt = 0; @@ -146,28 +186,31 @@ namespace NadekoBot.Modules.Music.Classes while (this.MusicPlayer.Paused) await Task.Delay(200, cancelToken).ConfigureAwait(false); + buffer = AdjustVolume(buffer, MusicPlayer.Volume); - voiceClient.Send(buffer, 0, read); + if (read != frameBytes) continue; + nextTime = unchecked(nextTime + milliseconds); + int delayMillis = unchecked(nextTime - Environment.TickCount); + if (delayMillis > 0) + await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false); + await outStream.WriteAsync(buffer, 0, read).ConfigureAwait(false); } } finally { await bufferTask; - await Task.Run(() => voiceClient.Clear()); if(inStream != null) inStream.Dispose(); - Console.WriteLine("l"); - sb.CleanFiles(); } } - private async Task CheckPrebufferingAsync(Stream inStream, SongBuffer sb, CancellationToken cancelToken) + private async Task CheckPrebufferingAsync(SongBuffer inStream, CancellationToken cancelToken, long size) { - while (!sb.BufferingCompleted && inStream.Length < 2.MiB()) + while (!inStream.BufferingCompleted && inStream.Length < size) { await Task.Delay(100, cancelToken); } - Console.WriteLine("Buffering successfull"); + _log.Debug("Buffering successfull"); } /* @@ -257,7 +300,8 @@ namespace NadekoBot.Modules.Music.Classes Provider = "Radio Stream", ProviderType = musicType, Query = query - }); + }) + { TotalLength = TimeSpan.MaxValue }; } if (SoundCloud.Default.IsSoundCloudLink(query)) { @@ -269,7 +313,8 @@ namespace NadekoBot.Modules.Music.Classes Uri = svideo.StreamLink, ProviderType = musicType, Query = svideo.TrackLink, - }); + }) + { TotalLength = TimeSpan.FromMilliseconds(svideo.Duration) }; } if (musicType == MusicType.Soundcloud) @@ -282,37 +327,26 @@ namespace NadekoBot.Modules.Music.Classes Uri = svideo.StreamLink, ProviderType = MusicType.Normal, Query = svideo.TrackLink, - }); + }) + { TotalLength = TimeSpan.FromMilliseconds(svideo.Duration) }; } - var link = await SearchHelper.FindYoutubeUrlByKeywords(query).ConfigureAwait(false); + var link = (await NadekoBot.Google.GetVideosByKeywordsAsync(query).ConfigureAwait(false)).FirstOrDefault(); if (string.IsNullOrWhiteSpace(link)) throw new OperationCanceledException("Not a valid youtube query."); - var allVideos = await Task.Factory.StartNew(async () => await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false)).Unwrap().ConfigureAwait(false); + var allVideos = await Task.Run(async () => { try { return await YouTube.Default.GetAllVideosAsync(link).ConfigureAwait(false); } catch { return Enumerable.Empty(); } }).ConfigureAwait(false); var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio); var video = videos - .Where(v => v.AudioBitrate < 192) + .Where(v => v.AudioBitrate < 256) .OrderByDescending(v => v.AudioBitrate) .FirstOrDefault(); if (video == null) // do something with this error throw new Exception("Could not load any video elements based on the query."); - - var m = Regex.Match(query, @"\?t=((?\d*)h)?((?\d*)m)?((?\d*)s?)?"); + var m = Regex.Match(query, @"\?t=(?\d*)"); int gotoTime = 0; if (m.Captures.Count > 0) - { - int hours; - int minutes; - int seconds; - - int.TryParse(m.Groups["h"].ToString(), out hours); - int.TryParse(m.Groups["m"].ToString(), out minutes); - int.TryParse(m.Groups["s"].ToString(), out seconds); - - gotoTime = hours * 60 * 60 + minutes * 60 + seconds; - } - + int.TryParse(m.Groups["t"].ToString(), out gotoTime); var song = new Song(new SongInfo { Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube" @@ -336,7 +370,10 @@ namespace NadekoBot.Modules.Music.Classes string file = null; try { - file = await SearchHelper.GetResponseStringAsync(query).ConfigureAwait(false); + using (var http = new HttpClient()) + { + file = await http.GetStringAsync(query).ConfigureAwait(false); + } } catch { diff --git a/NadekoBot/Modules/Music/Classes/SongBuffer.cs b/src/NadekoBot/Modules/Music/Classes/SongBuffer.cs similarity index 67% rename from NadekoBot/Modules/Music/Classes/SongBuffer.cs rename to src/NadekoBot/Modules/Music/Classes/SongBuffer.cs index d9192940..0b8e7849 100644 --- a/NadekoBot/Modules/Music/Classes/SongBuffer.cs +++ b/src/NadekoBot/Modules/Music/Classes/SongBuffer.cs @@ -1,11 +1,8 @@ using NadekoBot.Extensions; +using NLog; using System; -using System.Collections.Concurrent; -using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; @@ -15,23 +12,28 @@ namespace NadekoBot.Modules.Music.Classes /// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. /// It also help for large music by deleting files that are already seen. /// - class SongBuffer + class SongBuffer : Stream { - - public SongBuffer(string basename, SongInfo songInfo, int skipTo) + public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize) { + MusicPlayer = musicPlayer; Basename = basename; SongInfo = songInfo; SkipTo = skipTo; + MaxFileSize = maxFileSize; + CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); + _log = LogManager.GetCurrentClassLogger(); } + MusicPlayer MusicPlayer; + private string Basename; private SongInfo SongInfo; private int SkipTo; - private static int MAX_FILE_SIZE = 20.MiB(); + private int MaxFileSize = 2.MiB(); private long FileNumber = -1; @@ -41,8 +43,11 @@ namespace NadekoBot.Modules.Music.Classes private ulong CurrentBufferSize = 0; + private FileStream CurrentFileStream; + private Logger _log; + public Task BufferSong(CancellationToken cancelToken) => - Task.Factory.StartNew(async () => + Task.Run(async () => { Process p = null; FileStream outStream = null; @@ -66,7 +71,7 @@ namespace NadekoBot.Modules.Music.Classes while (!p.HasExited) //Also fix low bandwidth { int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false); - if (currentFileSize >= MAX_FILE_SIZE) + if (currentFileSize >= MaxFileSize) { try { @@ -116,13 +121,13 @@ Check the guides for your platform on how to setup ffmpeg correctly: p.Dispose(); } } - }, TaskCreationOptions.LongRunning); + }); /// /// Return the next file to read, and delete the old one /// /// Name of the file to read - public string GetNextFile() + private string GetNextFile() { string filename = Basename + "-" + NextFileToRead; @@ -139,12 +144,12 @@ Check the guides for your platform on how to setup ffmpeg correctly: return filename; } - public bool IsNextFileReady() + private bool IsNextFileReady() { return NextFileToRead <= FileNumber; } - public void CleanFiles() + private void CleanFiles() { for (long i = NextFileToRead - 1 ; i <= FileNumber; i++) { @@ -155,5 +160,59 @@ Check the guides for your platform on how to setup ffmpeg correctly: catch { } } } + + //Stream part + + public override bool CanRead => true; + + public override bool CanSeek => false; + + public override bool CanWrite => false; + + public override long Length => (long) CurrentBufferSize; + + public override long Position { get; set; } = 0; + + public override void Flush() { } + + public override int Read(byte[] buffer, int offset, int count) + { + int read = CurrentFileStream.Read(buffer, offset, count); + if(read < count) + { + if (!BufferingCompleted || IsNextFileReady()) + { + CurrentFileStream.Dispose(); + CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); + read += CurrentFileStream.Read(buffer, read + offset, count - read); + } + if (read < count) + Array.Clear(buffer, read, count - read); + } + return read; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public new void Dispose() + { + CurrentFileStream.Dispose(); + MusicPlayer.SongCancelSource.Cancel(); + CleanFiles(); + base.Dispose(); + } } } diff --git a/NadekoBot/Modules/Music/Classes/SoundCloud.cs b/src/NadekoBot/Modules/Music/Classes/SoundCloud.cs similarity index 78% rename from NadekoBot/Modules/Music/Classes/SoundCloud.cs rename to src/NadekoBot/Modules/Music/Classes/SoundCloud.cs index e743fa5c..8da2a13b 100644 --- a/NadekoBot/Modules/Music/Classes/SoundCloud.cs +++ b/src/NadekoBot/Modules/Music/Classes/SoundCloud.cs @@ -1,7 +1,7 @@ -using NadekoBot.Classes; -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Linq; +using System.Net.Http; using System.Threading.Tasks; namespace NadekoBot.Modules.Music.Classes @@ -18,10 +18,17 @@ namespace NadekoBot.Modules.Music.Classes { if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url)); - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.SoundCloudClientID)) - throw new ArgumentNullException(nameof(NadekoBot.Creds.SoundCloudClientID)); + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId)) + throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId)); - var response = await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false); + string response = ""; + + using (var http = new HttpClient()) + { + response = await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false); + + } + var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject(response); if (responseObj?.Kind != "track") @@ -33,14 +40,18 @@ namespace NadekoBot.Modules.Music.Classes public bool IsSoundCloudLink(string url) => System.Text.RegularExpressions.Regex.IsMatch(url, "(.*)(soundcloud.com|snd.sc)(.*)"); - internal async Task GetVideoByQueryAsync(string query) + public async Task GetVideoByQueryAsync(string query) { if (string.IsNullOrWhiteSpace(query)) throw new ArgumentNullException(nameof(query)); - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.SoundCloudClientID)) - throw new ArgumentNullException(nameof(NadekoBot.Creds.SoundCloudClientID)); + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.SoundCloudClientId)) + throw new ArgumentNullException(nameof(NadekoBot.Credentials.SoundCloudClientId)); - var response = await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false); + var response = ""; + using (var http = new HttpClient()) + { + response = await http.GetStringAsync($"http://api.soundcloud.com/tracks?q={Uri.EscapeDataString(query)}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false); + } var responseObj = JsonConvert.DeserializeObject(response).Where(s => s.Streamable).FirstOrDefault(); if (responseObj?.Kind != "track") @@ -59,10 +70,11 @@ namespace NadekoBot.Modules.Music.Classes [JsonIgnore] public string FullName => User.Name + " - " + Title; public bool Streamable { get; set; } = false; + public int Duration { get; set; } [JsonProperty("permalink_url")] public string TrackLink { get; set; } = ""; [JsonIgnore] - public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Creds.SoundCloudClientID}"; + public string StreamLink => $"https://api.soundcloud.com/tracks/{Id}/stream?client_id={NadekoBot.Credentials.SoundCloudClientId}"; } public class SoundCloudUser { diff --git a/src/NadekoBot/Modules/Music/Music.cs b/src/NadekoBot/Modules/Music/Music.cs new file mode 100644 index 00000000..de2baa84 --- /dev/null +++ b/src/NadekoBot/Modules/Music/Music.cs @@ -0,0 +1,826 @@ +using Discord.Commands; +using NadekoBot.Modules.Music.Classes; +using System.Collections.Concurrent; +using Discord.WebSocket; +using NadekoBot.Services; +using System.IO; +using Discord; +using System.Threading.Tasks; +using NadekoBot.Attributes; +using System; +using System.Linq; +using NadekoBot.Extensions; +using System.Net.Http; +using Newtonsoft.Json.Linq; +using System.Collections.Generic; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Modules.Music +{ + [NadekoModule("Music", "!!", AutoLoad = false)] + public partial class Music : DiscordModule + { + public static ConcurrentDictionary MusicPlayers = new ConcurrentDictionary(); + + public const string MusicDataPath = "data/musicdata"; + private IGoogleApiService _google; + + public Music(ILocalization loc, CommandService cmds, ShardedDiscordClient client, IGoogleApiService google) : base(loc, cmds, client) + { + //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); + + _google = google; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public Task Next(IUserMessage umsg, int skipCount = 1) + { + var channel = (ITextChannel)umsg.Channel; + + if (skipCount < 1) + return Task.CompletedTask; + + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return Task.CompletedTask; + if (musicPlayer.PlaybackVoiceChannel == ((IGuildUser)umsg.Author).VoiceChannel) + { + while (--skipCount > 0) + { + musicPlayer.RemoveSongAt(0); + } + musicPlayer.Next(); + } + return Task.CompletedTask; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public Task Stop(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return Task.CompletedTask; + if (((IGuildUser)umsg.Author).VoiceChannel == musicPlayer.PlaybackVoiceChannel) + { + musicPlayer.Autoplay = false; + musicPlayer.Stop(); + } + return Task.CompletedTask; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public Task Destroy(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return Task.CompletedTask; + if (((IGuildUser)umsg.Author).VoiceChannel == musicPlayer.PlaybackVoiceChannel) + if(MusicPlayers.TryRemove(channel.Guild.Id, out musicPlayer)) + musicPlayer.Destroy(); + return Task.CompletedTask; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Pause(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return; + if (((IGuildUser)umsg.Author).VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; + musicPlayer.TogglePause(); + if (musicPlayer.Paused) + await channel.SendMessageAsync("🎵`Music Player paused.`").ConfigureAwait(false); + else + await channel.SendMessageAsync("🎵`Music Player unpaused.`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Queue(IUserMessage umsg, [Remainder] string query) + { + var channel = (ITextChannel)umsg.Channel; + + await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, query).ConfigureAwait(false); + if (channel.Guild.GetCurrentUser().GetPermissions(channel).ManageMessages) + { + await Task.Delay(10000).ConfigureAwait(false); + await ((IUserMessage)umsg).DeleteAsync().ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SoundCloudQueue(IUserMessage umsg, [Remainder] string query) + { + var channel = (ITextChannel)umsg.Channel; + + await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, query, musicType: MusicType.Soundcloud).ConfigureAwait(false); + if (channel.Guild.GetCurrentUser().GetPermissions(channel).ManageMessages) + { + await Task.Delay(10000).ConfigureAwait(false); + await ((IUserMessage)umsg).DeleteAsync().ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ListQueue(IUserMessage umsg, int page = 1) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + { + await channel.SendMessageAsync("🎵 No active music player.").ConfigureAwait(false); + return; + } + if (page <= 0) + return; + + var currentSong = musicPlayer.CurrentSong; + if (currentSong == null) + return; + + if (currentSong.TotalLength == TimeSpan.Zero) + { + await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); + } + + var toSend = $"🎵`Now Playing` {currentSong.PrettyName} " + $"{currentSong.PrettyCurrentTime()}\n"; + if (musicPlayer.RepeatSong) + toSend += "🔂"; + else if (musicPlayer.RepeatPlaylist) + toSend += "🔁"; + toSend += $" **{musicPlayer.Playlist.Count}** `tracks currently queued. Showing page {page}` "; + if (musicPlayer.MaxQueueSize != 0 && musicPlayer.Playlist.Count >= musicPlayer.MaxQueueSize) + toSend += "**Song queue is full!**\n"; + else + toSend += "\n"; + const int itemsPerPage = 15; + int startAt = itemsPerPage * (page - 1); + var number = 1 + startAt; + await channel.SendMessageAsync(toSend + string.Join("\n", musicPlayer.Playlist.Skip(startAt).Take(15).Select(v => $"`{number++}.` {v.PrettyName}"))).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task NowPlaying(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + var currentSong = musicPlayer.CurrentSong; + if (currentSong == null) + return; + + if (currentSong.TotalLength == TimeSpan.Zero) + { + await musicPlayer.UpdateSongDurationsAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"🎵`Now Playing` {currentSong.PrettyName} " + + $"{currentSong.PrettyCurrentTime()}").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Volume(IUserMessage umsg, int val) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + if (((IGuildUser)umsg.Author).VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; + if (val < 0) + return; + var volume = musicPlayer.SetVolume(val); + await channel.SendMessageAsync($"🎵 `Volume set to {volume}%`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Defvol(IUserMessage umsg, [Remainder] int val) + { + var channel = (ITextChannel)umsg.Channel; + + if (val < 0 || val > 100) + { + await channel.SendMessageAsync("Volume number invalid. Must be between 0 and 100").ConfigureAwait(false); + return; + } + using (var uow = DbHandler.UnitOfWork()) + { + uow.GuildConfigs.For(channel.Guild.Id).DefaultMusicVolume = val / 100.0f; + uow.Complete(); + } + await channel.SendMessageAsync($"🎵 `Default volume set to {val}%`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ShufflePlaylist(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + if (((IGuildUser)umsg.Author).VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; + if (musicPlayer.Playlist.Count < 2) + { + await channel.SendMessageAsync("💢 Not enough songs in order to perform the shuffle.").ConfigureAwait(false); + return; + } + + musicPlayer.Shuffle(); + await channel.SendMessageAsync("🎵 `Songs shuffled.`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Playlist(IUserMessage umsg, [Remainder] string playlist) + { + var channel = (ITextChannel)umsg.Channel; + var arg = playlist; + if (string.IsNullOrWhiteSpace(arg)) + return; + if (((IGuildUser)umsg.Author).VoiceChannel?.Guild != channel.Guild) + { + await channel.SendMessageAsync("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.").ConfigureAwait(false); + return; + } + var plId = (await _google.GetPlaylistIdsByKeywordsAsync(arg).ConfigureAwait(false)).FirstOrDefault(); + if (plId == null) + { + await channel.SendMessageAsync("No search results for that query."); + return; + } + var ids = await _google.GetPlaylistTracksAsync(plId, 500).ConfigureAwait(false); + if (!ids.Any()) + { + await channel.SendMessageAsync($"🎵 `Failed to find any songs.`").ConfigureAwait(false); + return; + } + var idArray = ids as string[] ?? ids.ToArray(); + var count = idArray.Length; + var msg = + await channel.SendMessageAsync($"🎵 `Attempting to queue {count} songs".SnPl(count) + "...`").ConfigureAwait(false); + foreach (var id in idArray) + { + try + { + await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, id, true).ConfigureAwait(false); + } + catch (SongNotFoundException) { } + catch { break; } + } + await msg.ModifyAsync(m => m.Content = "🎵 `Playlist queue complete.`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SoundCloudPl(IUserMessage umsg, [Remainder] string pl) + { + var channel = (ITextChannel)umsg.Channel; + pl = pl?.Trim(); + + if (string.IsNullOrWhiteSpace(pl)) + return; + + using (var http = new HttpClient()) + { + var scvids = JObject.Parse(await http.GetStringAsync($"http://api.soundcloud.com/resolve?url={pl}&client_id={NadekoBot.Credentials.SoundCloudClientId}").ConfigureAwait(false))["tracks"].ToObject(); + await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, scvids[0].TrackLink).ConfigureAwait(false); + + MusicPlayer mp; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out mp)) + return; + + foreach (var svideo in scvids.Skip(1)) + { + try + { + mp.AddSong(new Song(new Classes.SongInfo + { + Title = svideo.FullName, + Provider = "SoundCloud", + Uri = svideo.StreamLink, + ProviderType = MusicType.Normal, + Query = svideo.TrackLink, + }), ((IGuildUser)umsg.Author).Username); + } + catch (PlaylistFullException) { break; } + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task LocalPl(IUserMessage umsg, [Remainder] string directory) + { + var channel = (ITextChannel)umsg.Channel; + var arg = directory; + if (string.IsNullOrWhiteSpace(arg)) + return; + try + { + var dir = new DirectoryInfo(arg); + var fileEnum = dir.GetFiles("*", SearchOption.AllDirectories) + .Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden | FileAttributes.System)); + foreach (var file in fileEnum) + { + try + { + await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, file.FullName, true, MusicType.Local).ConfigureAwait(false); + } + catch (PlaylistFullException) + { + break; + } + catch { } + } + await channel.SendMessageAsync("🎵 `Directory queue complete.`").ConfigureAwait(false); + } + catch { } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Radio(IUserMessage umsg, string radio_link) + { + var channel = (ITextChannel)umsg.Channel; + if (((IGuildUser)umsg.Author).VoiceChannel?.Guild != channel.Guild) + { + await channel.SendMessageAsync("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.").ConfigureAwait(false); + return; + } + await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, radio_link, musicType: MusicType.Radio).ConfigureAwait(false); + if (channel.Guild.GetCurrentUser().GetPermissions(channel).ManageMessages) + { + await Task.Delay(10000).ConfigureAwait(false); + await ((IUserMessage)umsg).DeleteAsync().ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task Local(IUserMessage umsg, [Remainder] string path) + { + var channel = (ITextChannel)umsg.Channel; + var arg = path; + if (string.IsNullOrWhiteSpace(arg)) + return; + await QueueSong(((IGuildUser)umsg.Author), channel, ((IGuildUser)umsg.Author).VoiceChannel, path, musicType: MusicType.Local).ConfigureAwait(false); + + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Move(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + var voiceChannel = ((IGuildUser)umsg.Author).VoiceChannel; + if (voiceChannel == null || voiceChannel.Guild != channel.Guild || !MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + await musicPlayer.MoveToVoiceChannel(voiceChannel); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(0)] + public async Task Remove(IUserMessage umsg, int num) + { + var channel = (ITextChannel)umsg.Channel; + + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + { + return; + } + if (((IGuildUser)umsg.Author).VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; + if (num <= 0 || num > musicPlayer.Playlist.Count) + return; + var song = (musicPlayer.Playlist as List)?[num - 1]; + musicPlayer.RemoveSongAt(num - 1); + await channel.SendMessageAsync($"🎵**Track {song.PrettyName} at position `#{num}` has been removed.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(1)] + public async Task Remove(IUserMessage umsg, string all) + { + var channel = (ITextChannel)umsg.Channel; + + if (all.Trim().ToUpperInvariant() != "ALL") + return; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) return; + musicPlayer.ClearQueue(); + await channel.SendMessageAsync($"🎵`Queue cleared!`").ConfigureAwait(false); + return; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task MoveSong(IUserMessage umsg, [Remainder] string fromto) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + { + return; + } + fromto = fromto?.Trim(); + var fromtoArr = fromto.Split('>'); + + int n1; + int n2; + + var playlist = musicPlayer.Playlist as List ?? musicPlayer.Playlist.ToList(); + + if (fromtoArr.Length != 2 || !int.TryParse(fromtoArr[0], out n1) || + !int.TryParse(fromtoArr[1], out n2) || n1 < 1 || n2 < 1 || n1 == n2 || + n1 > playlist.Count || n2 > playlist.Count) + { + await channel.SendMessageAsync("`Invalid input.`").ConfigureAwait(false); + return; + } + + var s = playlist[n1 - 1]; + playlist.Insert(n2 - 1, s); + var nn1 = n2 < n1 ? n1 : n1 - 1; + playlist.RemoveAt(nn1); + + await channel.SendMessageAsync($"🎵`Moved` {s.PrettyName} `from #{n1} to #{n2}`").ConfigureAwait(false); + + + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SetMaxQueue(IUserMessage umsg, uint size) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + { + return; + } + musicPlayer.MaxQueueSize = size; + await channel.SendMessageAsync($"🎵 `Max queue set to {(size == 0 ? ("unlimited") : size + " tracks")}`"); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ReptCurSong(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + var currentSong = musicPlayer.CurrentSong; + if (currentSong == null) + return; + var currentValue = musicPlayer.ToggleRepeatSong(); + await channel.SendMessageAsync(currentValue ? + $"🎵🔂`Repeating track:`{currentSong.PrettyName}" : + $"🎵🔂`Current track repeat stopped.`") + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task RepeatPl(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + var currentValue = musicPlayer.ToggleRepeatPlaylist(); + await channel.SendMessageAsync($"🎵🔁`Repeat playlist {(currentValue ? "enabled" : "disabled")}`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Save(IUserMessage umsg, [Remainder] string name) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + + var curSong = musicPlayer.CurrentSong; + var songs = musicPlayer.Playlist.Append(curSong) + .Select(s=> new PlaylistSong() { + Provider = s.SongInfo.Provider, + ProviderType = s.SongInfo.ProviderType, + Title = s.SongInfo.Title, + Uri = s.SongInfo.Uri, + Query = s.SongInfo.Query, + }).ToList(); + + MusicPlaylist playlist; + using (var uow = DbHandler.UnitOfWork()) + { + playlist = new MusicPlaylist + { + Name = name, + Author = umsg.Author.Username, + AuthorId = umsg.Author.Id, + Songs = songs, + }; + uow.MusicPlaylists.Add(playlist); + await uow.CompleteAsync().ConfigureAwait(false); + } + + await channel.SendMessageAsync(($"🎵 `Saved playlist as {name}.` `Id: {playlist.Id}`")).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Load(IUserMessage umsg, [Remainder] int id) + { + var channel = (ITextChannel)umsg.Channel; + + MusicPlaylist mpl; + using (var uow = DbHandler.UnitOfWork()) + { + mpl = uow.MusicPlaylists.GetWithSongs(id); + } + + if (mpl == null) + { + await channel.SendMessageAsync("`Can't find playlist with that ID`").ConfigureAwait(false); + return; + } + IUserMessage msg = null; + try { msg = await channel.SendMessageAsync($"`Attempting to load {mpl.Songs.Count} songs...`").ConfigureAwait(false); } catch (Exception ex) { _log.Warn(ex); } + foreach (var item in mpl.Songs) + { + var usr = (IGuildUser)umsg.Author; + try + { + await QueueSong(usr, channel, usr.VoiceChannel, item.Query, true, item.ProviderType).ConfigureAwait(false); + } + catch (SongNotFoundException) { } + catch { break; } + } + if (msg != null) + await msg.ModifyAsync(m => m.Content = $"`Done loading playlist {mpl.Name}.`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Playlists(IUserMessage umsg, [Remainder] int num = 1) + { + var channel = (ITextChannel)umsg.Channel; + + if (num <= 0) + return; + + List playlists; + + using (var uow = DbHandler.UnitOfWork()) + { + playlists = uow.MusicPlaylists.GetPlaylistsOnPage(num); + } + + await channel.SendMessageAsync($@"`Page {num} of saved playlists` + +" + string.Join("\n", playlists.Select(r => $"`#{r.Id}` - `{r.Name}` by {r.Author} - **{r.Songs.Count}** songs"))).ConfigureAwait(false); + } + + //todo only author or owner + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task DeletePlaylist(IUserMessage umsg, [Remainder] int id) + { + var channel = (ITextChannel)umsg.Channel; + + bool success = false; + MusicPlaylist pl = null; + try + { + using (var uow = DbHandler.UnitOfWork()) + { + pl = uow.MusicPlaylists.Get(id); + + if (pl != null) + { + if (NadekoBot.Credentials.IsOwner(umsg.Author) || pl.AuthorId == umsg.Author.Id) + { + uow.MusicPlaylists.Remove(pl); + await uow.CompleteAsync().ConfigureAwait(false); + success = true; + } + else + success = false; + } + } + + if (!success) + await channel.SendMessageAsync("Failed to delete that playlist. It either doesn't exist, or you are not its author.").ConfigureAwait(false); + else + await channel.SendMessageAsync("`Playlist successfully deleted.`").ConfigureAwait(false); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Goto(IUserMessage umsg, int time) + { + var channel = (ITextChannel)umsg.Channel; + + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + if (((IGuildUser)umsg.Author).VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; + + if (time < 0) + return; + + var currentSong = musicPlayer.CurrentSong; + + if (currentSong == null) + return; + + //currentSong.PrintStatusMessage = false; + var gotoSong = currentSong.Clone(); + gotoSong.SkipTo = time; + musicPlayer.AddSong(gotoSong, 0); + musicPlayer.Next(); + + var minutes = (time / 60).ToString(); + var seconds = (time % 60).ToString(); + + if (minutes.Length == 1) + minutes = "0" + minutes; + if (seconds.Length == 1) + seconds = "0" + seconds; + + await channel.SendMessageAsync($"`Skipped to {minutes}:{seconds}`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task GetLink(IUserMessage umsg, int index = 0) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + + if (index < 0) + return; + + if (index > 0) + { + + var selSong = musicPlayer.Playlist.DefaultIfEmpty(null).ElementAtOrDefault(index - 1); + if (selSong == null) + { + await channel.SendMessageAsync("Could not select song, likely wrong index"); + + } + else + { + await channel.SendMessageAsync($"🎶`Selected song {selSong.SongInfo.Title}:` <{selSong.SongInfo.Query}>").ConfigureAwait(false); + } + } + else + { + var curSong = musicPlayer.CurrentSong; + if (curSong == null) + return; + await channel.SendMessageAsync($"🎶`Current song:` <{curSong.SongInfo.Query}>").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Autoplay(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(channel.Guild.Id, out musicPlayer)) + return; + + if (!musicPlayer.ToggleAutoplay()) + await channel.SendMessageAsync("🎶`Autoplay disabled.`").ConfigureAwait(false); + else + await channel.SendMessageAsync("🎶`Autoplay enabled.`").ConfigureAwait(false); + } + + public static async Task QueueSong(IGuildUser queuer, ITextChannel textCh, IVoiceChannel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) + { + if (voiceCh == null || voiceCh.Guild != textCh.Guild) + { + if (!silent) + await textCh.SendMessageAsync("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining.").ConfigureAwait(false); + throw new ArgumentNullException(nameof(voiceCh)); + } + if (string.IsNullOrWhiteSpace(query) || query.Length < 3) + throw new ArgumentException("💢 Invalid query for queue song.", nameof(query)); + + var musicPlayer = MusicPlayers.GetOrAdd(textCh.Guild.Id, server => + { + float vol = 1;// SpecificConfigurations.Default.Of(server.Id).DefaultMusicVolume; + using (var uow = DbHandler.UnitOfWork()) + { + vol = uow.GuildConfigs.For(textCh.Guild.Id).DefaultMusicVolume; + } + var mp = new MusicPlayer(voiceCh, vol); + + + IUserMessage playingMessage = null; + IUserMessage lastFinishedMessage = null; + mp.OnCompleted += async (s, song) => + { + if (song.PrintStatusMessage) + { + try + { + if (lastFinishedMessage != null) + await lastFinishedMessage.DeleteAsync().ConfigureAwait(false); + if (playingMessage != null) + await playingMessage.DeleteAsync().ConfigureAwait(false); + try { lastFinishedMessage = await textCh.SendMessageAsync($"🎵`Finished`{song.PrettyName}").ConfigureAwait(false); } catch { } + if (mp.Autoplay && mp.Playlist.Count == 0 && song.SongInfo.Provider == "YouTube") + { + await QueueSong(queuer.Guild.GetCurrentUser(), textCh, voiceCh, (await NadekoBot.Google.GetRelatedVideosAsync(song.SongInfo.Query, 4)).ToList().Shuffle().FirstOrDefault(), silent, musicType).ConfigureAwait(false); + } + } + catch { } + } + }; + mp.OnStarted += async (s, song) => + { + if (song.PrintStatusMessage) + { + var sender = s as MusicPlayer; + if (sender == null) + return; + + var msgTxt = $"🎵`Playing`{song.PrettyName} `Vol: {(int)(sender.Volume * 100)}%`"; + try { playingMessage = await textCh.SendMessageAsync(msgTxt).ConfigureAwait(false); } catch { } + } + }; + return mp; + }); + Song resolvedSong; + try + { + musicPlayer.ThrowIfQueueFull(); + resolvedSong = await Song.ResolveSong(query, musicType).ConfigureAwait(false); + + if (resolvedSong == null) + throw new SongNotFoundException(); + + musicPlayer.AddSong(resolvedSong, queuer.Username); + } + catch (PlaylistFullException) + { + try { await textCh.SendMessageAsync($"🎵 `Queue is full at {musicPlayer.MaxQueueSize}/{musicPlayer.MaxQueueSize}.` "); } catch { } + throw; + } + if (!silent) + { + try + { + var queuedMessage = await textCh.SendMessageAsync($"🎵`Queued`{resolvedSong.PrettyName} **at** `#{musicPlayer.Playlist.Count + 1}`").ConfigureAwait(false); + var t = Task.Run(async () => + { + try + { + await Task.Delay(10000).ConfigureAwait(false); + + await queuedMessage.DeleteAsync().ConfigureAwait(false); + } + catch { } + }).ConfigureAwait(false); + } + catch { } // if queued message sending fails, don't attempt to delete it + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/NSFW/NSFW.cs b/src/NadekoBot/Modules/NSFW/NSFW.cs new file mode 100644 index 00000000..4c16efbb --- /dev/null +++ b/src/NadekoBot/Modules/NSFW/NSFW.cs @@ -0,0 +1,330 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; +using System.Threading.Tasks; +using NadekoBot.Services; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using NadekoBot.Extensions; + +namespace NadekoBot.Modules.NSFW +{ + [NadekoModule("NSFW", "~")] + public class NSFW : DiscordModule + { + public NSFW(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Hentai(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + + tag = "rating%3Aexplicit+" + tag; + + var rng = new NadekoRandom(); + Task provider = Task.FromResult(""); + switch (rng.Next(0,4)) + { + case 0: + provider = GetDanbooruImageLink(tag); + break; + case 1: + provider = GetGelbooruImageLink(tag); + break; + case 2: + provider = GetKonachanImageLink(tag); + break; + case 3: + provider = GetYandereImageLink(tag); + break; + default: + break; + } + var link = await provider.ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(link)) + await channel.SendMessageAsync("Search yielded no results ;(").ConfigureAwait(false); + else + await channel.SendMessageAsync(link).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task HentaiBomb(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + tag = "rating%3Aexplicit+" + tag; + + var links = await Task.WhenAll(GetGelbooruImageLink(tag), + GetDanbooruImageLink(tag), + GetKonachanImageLink(tag), + GetYandereImageLink(tag)).ConfigureAwait(false); + + if (links.All(l => l == null)) + { + await channel.SendMessageAsync("`No results.`").ConfigureAwait(false); + return; + } + + await channel.SendMessageAsync(String.Join("\n\n", links)).ConfigureAwait(false); + } + + public static async Task GetYandereImageLink(string tag) + { + var rng = new NadekoRandom(); + var url = + $"https://yande.re/post.xml?" + + $"limit=25" + + $"&page={rng.Next(0, 15)}" + + $"&tags={tag.Replace(" ", "_")}"; + using (var http = new HttpClient()) + { + var webpage = await http.GetStringAsync(url).ConfigureAwait(false); + var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); + //var rating = Regex.Matches(webpage, "rating=\"(?.*?)\""); + if (matches.Count == 0) + return null; + return matches[rng.Next(0, matches.Count)].Groups["url"].Value; + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Yandere(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + var link = await GetYandereImageLink(tag).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(link)) + await channel.SendMessageAsync("Search yielded no results ;(").ConfigureAwait(false); + else + await channel.SendMessageAsync(link).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Danbooru(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + var link = await GetDanbooruImageLink(tag).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(link)) + await channel.SendMessageAsync("Search yielded no results ;(").ConfigureAwait(false); + else + await channel.SendMessageAsync(link).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Konachan(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + var link = await GetKonachanImageLink(tag).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(link)) + await channel.SendMessageAsync("Search yielded no results ;(").ConfigureAwait(false); + else + await channel.SendMessageAsync(link).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Gelbooru(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + var link = await GetGelbooruImageLink(tag).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(link)) + await channel.SendMessageAsync("Search yielded no results ;(").ConfigureAwait(false); + else + await channel.SendMessageAsync(link).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Rule34(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + var link = await GetRule34ImageLink(tag).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(link)) + await channel.SendMessageAsync("Search yielded no results ;(").ConfigureAwait(false); + else + await channel.SendMessageAsync(link).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task E621(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + var link = await GetE621ImageLink(tag).ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(link)) + await channel.SendMessageAsync("Search yielded no results ;(").ConfigureAwait(false); + else + await channel.SendMessageAsync(link).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Cp(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + await channel.SendMessageAsync("http://i.imgur.com/MZkY1md.jpg").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Boobs(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + try + { + JToken obj; + using (var http = new HttpClient()) + { + obj = JArray.Parse(await http.GetStringAsync($"http://api.oboobs.ru/boobs/{ new NadekoRandom().Next(0, 9880) }").ConfigureAwait(false))[0]; + } + await channel.SendMessageAsync($"http://media.oboobs.ru/{ obj["preview"].ToString() }").ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync($"💢 {ex.Message}").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Butts(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + JToken obj; + using (var http = new HttpClient()) + { + obj = JArray.Parse(await http.GetStringAsync($"http://api.obutts.ru/butts/{ new NadekoRandom().Next(0, 3873) }").ConfigureAwait(false))[0]; + } + await channel.SendMessageAsync($"http://media.obutts.ru/{ obj["preview"].ToString() }").ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync($"💢 {ex.Message}").ConfigureAwait(false); + } + } + + public static async Task GetKonachanImageLink(string tag) + { + var rng = new NadekoRandom(); + + var link = $"http://konachan.com/post?" + + $"page={rng.Next(0, 5)}"; + if (!string.IsNullOrWhiteSpace(tag)) + link += $"&tags={tag.Replace(" ", "_")}"; + using (var http = new HttpClient()) + { + var webpage = await http.GetStringAsync(link).ConfigureAwait(false); + var matches = Regex.Matches(webpage, ".*?)\">"); + + if (matches.Count == 0) + return null; + return matches[rng.Next(0, matches.Count)].Groups["ll"].Value; + } + } + + public static async Task GetDanbooruImageLink(string tag) + { + var rng = new NadekoRandom(); + + if (tag == "loli") //loli doesn't work for some reason atm + tag = "flat_chest"; + + var link = $"http://danbooru.donmai.us/posts?" + + $"page={rng.Next(0, 15)}"; + if (!string.IsNullOrWhiteSpace(tag)) + link += $"&tags={tag.Replace(" ", "_")}"; + using (var http = new HttpClient()) + { + var webpage = await http.GetStringAsync(link).ConfigureAwait(false); + var matches = Regex.Matches(webpage, "data-large-file-url=\"(?.*?)\""); + + if (matches.Count == 0) + return null; + return $"http://danbooru.donmai.us" + + $"{matches[rng.Next(0, matches.Count)].Groups["id"].Value}"; + } + } + + public static async Task GetGelbooruImageLink(string tag) + { + using (var http = new HttpClient()) + { + http.AddFakeHeaders(); + + var webpage = await http.GetStringAsync("http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=100&tags="+ tag.Replace(" ", "_")).ConfigureAwait(false); + var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); + if (matches.Count == 0) + return null; + + var rng = new NadekoRandom(); + var match = matches[rng.Next(0, matches.Count)]; + return matches[rng.Next(0, matches.Count)].Groups["url"].Value; + } + } + + public static async Task GetRule34ImageLink(string tag) + { + var rng = new NadekoRandom(); + var url = + $"http://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag.Replace(" ", "_")}"; + using (var http = new HttpClient()) + { + var webpage = await http.GetStringAsync(url).ConfigureAwait(false); + var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); + if (matches.Count == 0) + return null; + var match = matches[rng.Next(0, matches.Count)]; + return "http:" + matches[rng.Next(0, matches.Count)].Groups["url"].Value; + } + } + + + public static async Task GetE621ImageLink(string tags) + { + try + { + using (var http = new HttpClient()) + { + http.AddFakeHeaders(); + var data = await http.GetStreamAsync("http://e621.net/post/index.xml?tags=" + Uri.EscapeUriString(tags) + "%20order:random&limit=1"); + var doc = XDocument.Load(data); + return doc.Descendants("file_url").FirstOrDefault().Value; + } + } + catch (Exception ex) + { + Console.WriteLine("Error in e621 search: \n" + ex); + return "Error, do you have too many tags?"; + } + } + } +} diff --git a/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs new file mode 100644 index 00000000..d218b9b0 --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/Commands/BlacklistCommands.cs @@ -0,0 +1,111 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Modules.Games.Trivia; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using static NadekoBot.Services.Database.Models.BlacklistItem; + +namespace NadekoBot.Modules.Permissions +{ + public partial class Permissions + { + public enum AddRemove + { + Add, + Rem + } + + [Group] + public class BlacklistCommands + { + public static ConcurrentHashSet BlacklistedItems { get; set; } = new ConcurrentHashSet(); + + static BlacklistCommands() + { + using (var uow = DbHandler.UnitOfWork()) + { + BlacklistedItems = new ConcurrentHashSet(uow.BotConfig.GetOrCreate().Blacklist); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public Task UserBlacklist(IUserMessage imsg, AddRemove action, ulong id) + => Blacklist(imsg, action, id, BlacklistType.User); + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public Task UserBlacklist(IUserMessage imsg, AddRemove action, IUser usr) + => Blacklist(imsg, action, usr.Id, BlacklistType.User); + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public Task ChannelBlacklist(IUserMessage imsg, AddRemove action, ulong id) + => Blacklist(imsg, action, id, BlacklistType.Channel); + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public Task ServerBlacklist(IUserMessage imsg, AddRemove action, ulong id) + => Blacklist(imsg, action, id, BlacklistType.Server); + + [NadekoCommand, Usage, Description, Aliases] + [OwnerOnly] + public Task ServerBlacklist(IUserMessage imsg, AddRemove action, IGuild guild) + => Blacklist(imsg, action, guild.Id, BlacklistType.Server); + + private async Task Blacklist(IUserMessage imsg, AddRemove action, ulong id, BlacklistType type) + { + var channel = imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + if (action == AddRemove.Add) + { + var item = new BlacklistItem { ItemId = id, Type = type }; + uow.BotConfig.GetOrCreate().Blacklist.Add(item); + BlacklistedItems.Add(item); + } + else + { + uow.BotConfig.GetOrCreate().Blacklist.RemoveWhere(bi => bi.ItemId == id && bi.Type == type); + BlacklistedItems.RemoveWhere(bi => bi.ItemId == id && bi.Type == type); + } + await uow.CompleteAsync().ConfigureAwait(false); + } + if (action == AddRemove.Add) + { + TriviaGame tg; + switch (type) + { + case BlacklistType.Server: + Games.Games.TriviaCommands.RunningTrivias.TryRemove(id, out tg); + if (tg != null) + { + await tg.StopGame().ConfigureAwait(false); + } + break; + case BlacklistType.Channel: + var item = Games.Games.TriviaCommands.RunningTrivias.FirstOrDefault(kvp => kvp.Value.channel.Id == id); + Games.Games.TriviaCommands.RunningTrivias.TryRemove(item.Key, out tg); + if (tg != null) + { + await tg.StopGame().ConfigureAwait(false); + } + break; + case BlacklistType.User: + break; + default: + break; + } + + } + + await channel.SendMessageAsync(":ok:").ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs new file mode 100644 index 00000000..28e3ec40 --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/Commands/CmdCdsCommands.cs @@ -0,0 +1,124 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Permissions +{ + public partial class Permissions + { + + public class ActiveCooldown + { + public string Command { get; set; } + public ulong UserId { get; set; } + } + + [Group] + public class CmdCdsCommands + { + public static ConcurrentDictionary> commandCooldowns { get; } + private static ConcurrentDictionary> activeCooldowns = new ConcurrentDictionary>(); + + static CmdCdsCommands() + { + using (var uow = DbHandler.UnitOfWork()) + { + var configs = NadekoBot.AllGuildConfigs; + commandCooldowns = new ConcurrentDictionary>(configs.ToDictionary(k => k.GuildId, v => new ConcurrentHashSet(v.CommandCooldowns))); + } + } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task CmdCooldown(IUserMessage imsg, Command command, int secs) + { + var channel = (ITextChannel)imsg.Channel; + if (secs < 0 || secs > 3600) + { + await channel.SendMessageAsync("⚠️ Invalid second parameter. (Must be a number between 0 and 3600)").ConfigureAwait(false); + return; + } + + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + + config.CommandCooldowns.RemoveWhere(cc => cc.CommandName == command.Text.ToLowerInvariant()); + localSet.RemoveWhere(cc => cc.CommandName == command.Text.ToLowerInvariant()); + if (secs != 0) + { + var cc = new CommandCooldown() + { + CommandName = command.Text.ToLowerInvariant(), + Seconds = secs, + }; + config.CommandCooldowns.Add(cc); + localSet.Add(cc); + } + await uow.CompleteAsync().ConfigureAwait(false); + } + if (secs == 0) + { + var activeCds = activeCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + activeCds.RemoveWhere(ac => ac.Command == command.Text.ToLowerInvariant()); + await channel.SendMessageAsync($"🚮 Command **{command}** has no coooldown now and all existing cooldowns have been cleared.").ConfigureAwait(false); + } + else + await channel.SendMessageAsync($"✅ Command **{command}** now has a **{secs} {(secs == 1 ? "second" : "seconds")}** cooldown.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task AllCmdCooldowns(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + var localSet = commandCooldowns.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + + if (!localSet.Any()) + await channel.SendMessageAsync("ℹ️ `No command cooldowns set.`").ConfigureAwait(false); + else + await channel.SendTableAsync("", localSet.Select(c => c.CommandName + ": " + c.Seconds + " secs"), s => $"{s,-30}", 2).ConfigureAwait(false); + } + + public static bool HasCooldown(Command cmd, IGuild guild, IUser user) + { + if (guild == null) + return false; + var cmdcds = CmdCdsCommands.commandCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet()); + CommandCooldown cdRule; + if ((cdRule = cmdcds.FirstOrDefault(cc => cc.CommandName == cmd.Text.ToLowerInvariant())) != null) + { + var activeCdsForGuild = activeCooldowns.GetOrAdd(guild.Id, new ConcurrentHashSet()); + if (activeCdsForGuild.FirstOrDefault(ac => ac.UserId == user.Id && ac.Command == cmd.Text.ToLowerInvariant()) != null) + { + return true; + } + else + { + activeCdsForGuild.Add(new ActiveCooldown() + { + UserId = user.Id, + Command = cmd.Text.ToLowerInvariant(), + }); + var t = Task.Run(async () => + { + try + { + await Task.Delay(cdRule.Seconds * 1000); + activeCdsForGuild.RemoveWhere(ac => ac.Command == cmd.Text.ToLowerInvariant() && ac.UserId == user.Id); + } + catch { } + }); + } + } + return false; + } + } + } +} diff --git a/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs b/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs new file mode 100644 index 00000000..c1e35d2d --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/Commands/FilterCommands.cs @@ -0,0 +1,234 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Permissions +{ + public partial class Permissions + { + [Group] + public class FilterCommands + { + public static ConcurrentHashSet InviteFilteringChannels { get; set; } + public static ConcurrentHashSet InviteFilteringServers { get; set; } + + //serverid, filteredwords + private static ConcurrentDictionary> ServerFilteredWords { get; set; } + + public static ConcurrentHashSet WordFilteringChannels { get; set; } + public static ConcurrentHashSet WordFilteringServers { get; set; } + + public static ConcurrentHashSet FilteredWordsForChannel(ulong channelId, ulong guildId) + { + ConcurrentHashSet words = new ConcurrentHashSet(); + if(WordFilteringChannels.Contains(channelId)) + ServerFilteredWords.TryGetValue(guildId, out words); + return words; + } + + public static ConcurrentHashSet FilteredWordsForServer(ulong guildId) + { + var words = new ConcurrentHashSet(); + if(WordFilteringServers.Contains(guildId)) + ServerFilteredWords.TryGetValue(guildId, out words); + return words; + } + + static FilterCommands() + { + using (var uow = DbHandler.UnitOfWork()) + { + var guildConfigs = NadekoBot.AllGuildConfigs; + + InviteFilteringServers = new ConcurrentHashSet(guildConfigs.Where(gc => gc.FilterInvites).Select(gc => gc.GuildId)); + InviteFilteringChannels = new ConcurrentHashSet(guildConfigs.SelectMany(gc => gc.FilterInvitesChannelIds.Select(fci => fci.ChannelId))); + + var dict = guildConfigs.ToDictionary(gc => gc.GuildId, gc => new ConcurrentHashSet(gc.FilteredWords.Select(fw => fw.Word))); + + ServerFilteredWords = new ConcurrentDictionary>(dict); + + var serverFiltering = guildConfigs.Where(gc => gc.FilterWords); + WordFilteringServers = new ConcurrentHashSet(serverFiltering.Select(gc => gc.GuildId)); + + WordFilteringChannels = new ConcurrentHashSet(guildConfigs.SelectMany(gc => gc.FilterWordsChannelIds.Select(fwci => fwci.ChannelId))); + + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SrvrFilterInv(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + enabled = config.FilterInvites = !config.FilterInvites; + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (enabled) + { + InviteFilteringServers.Add(channel.Guild.Id); + await channel.SendMessageAsync("✅ `Invite filtering enabled on this server.`").ConfigureAwait(false); + } + else + { + InviteFilteringServers.TryRemove(channel.Guild.Id); + await channel.SendMessageAsync("ℹ️ `Invite filtering disabled on this server.`").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ChnlFilterInv(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + int removed; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + removed = config.FilterInvitesChannelIds.RemoveWhere(fc => fc.ChannelId == channel.Id); + if (removed == 0) + { + config.FilterInvitesChannelIds.Add(new Services.Database.Models.FilterChannelId() + { + ChannelId = channel.Id + }); + } + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (removed == 0) + { + InviteFilteringChannels.Add(channel.Id); + await channel.SendMessageAsync("✅ `Invite filtering enabled on this channel.`").ConfigureAwait(false); + } + else + { + InviteFilteringChannels.TryRemove(channel.Id); + await channel.SendMessageAsync("ℹ️ `Invite filtering disabled on this channel.`").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SrvrFilterWords(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + bool enabled; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + enabled = config.FilterWords = !config.FilterWords; + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (enabled) + { + WordFilteringServers.Add(channel.Guild.Id); + await channel.SendMessageAsync("✅ `Word filtering enabled on this server.`").ConfigureAwait(false); + } + else + { + WordFilteringServers.TryRemove(channel.Guild.Id); + await channel.SendMessageAsync("ℹ️ `Word filtering disabled on this server.`").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ChnlFilterWords(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + int removed; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + removed = config.FilterWordsChannelIds.RemoveWhere(fc => fc.ChannelId == channel.Id); + if (removed == 0) + { + config.FilterWordsChannelIds.Add(new Services.Database.Models.FilterChannelId() + { + ChannelId = channel.Id + }); + } + await uow.CompleteAsync().ConfigureAwait(false); + } + + if (removed == 0) + { + WordFilteringChannels.Add(channel.Id); + await channel.SendMessageAsync("✅ `Word filtering enabled on this channel.`").ConfigureAwait(false); + } + else + { + WordFilteringChannels.TryRemove(channel.Id); + await channel.SendMessageAsync("ℹ️ `Word filtering disabled on this channel.`").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task FilterWord(IUserMessage imsg, [Remainder] string word) + { + var channel = (ITextChannel)imsg.Channel; + + word = word?.Trim().ToLowerInvariant(); + + if (string.IsNullOrWhiteSpace(word)) + return; + + int removed; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + + removed = config.FilteredWords.RemoveWhere(fw => fw.Word == word); + + if (removed == 0) + config.FilteredWords.Add(new Services.Database.Models.FilteredWord() { Word = word }); + + await uow.CompleteAsync().ConfigureAwait(false); + } + + var filteredWords = ServerFilteredWords.GetOrAdd(channel.Guild.Id, new ConcurrentHashSet()); + + if (removed == 0) + { + filteredWords.Add(word); + await channel.SendMessageAsync($"✅ Word `{word}` successfully added to the list of filtered words.") + .ConfigureAwait(false); + } + else + { + filteredWords.TryRemove(word); + await channel.SendMessageAsync($"ℹ️ Word `{word}` removed from the list of filtered words.") + .ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task LstFilterWords(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + ConcurrentHashSet filteredWords; + ServerFilteredWords.TryGetValue(channel.Guild.Id, out filteredWords); + + await channel.SendMessageAsync($"ℹ️ `List of banned words:`\n" + string.Join(",\n", filteredWords)) + .ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Permissions/PermissionAction.cs b/src/NadekoBot/Modules/Permissions/PermissionAction.cs new file mode 100644 index 00000000..324bbf44 --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/PermissionAction.cs @@ -0,0 +1,27 @@ +namespace NadekoBot.Modules.Permissions +{ + public class PermissionAction + { + public static PermissionAction Enable => new PermissionAction(true); + public static PermissionAction Disable => new PermissionAction(false); + + public bool Value { get; } + + public PermissionAction(bool value) + { + this.Value = value; + } + + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + return this.Value == ((PermissionAction)obj).Value; + } + + public override int GetHashCode() => Value.GetHashCode(); + } +} diff --git a/src/NadekoBot/Modules/Permissions/PermissionExtensions.cs b/src/NadekoBot/Modules/Permissions/PermissionExtensions.cs new file mode 100644 index 00000000..8340b2f5 --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/PermissionExtensions.cs @@ -0,0 +1,240 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace NadekoBot.Modules.Permissions +{ + public static class PermissionExtensions + { + public static bool CheckPermissions(this IEnumerable permsEnumerable, IUserMessage message, Command command) + { + var perms = permsEnumerable as List ?? permsEnumerable.ToList(); + int throwaway; + return perms.CheckPermissions(message, command.Name, command.Module.Name, out throwaway); + } + + public static bool CheckPermissions(this IEnumerable permsEnumerable, IUserMessage message, string commandName, string moduleName) + { + var perms = permsEnumerable as List ?? permsEnumerable.ToList(); + int throwaway; + return perms.CheckPermissions(message, commandName, moduleName, out throwaway); + } + + public static bool CheckPermissions(this IEnumerable permsEnumerable, IUserMessage message, string commandName, string moduleName, out int permIndex) + { + var perms = permsEnumerable as List ?? permsEnumerable.ToList(); + + for (int i = 0; i < perms.Count; i++) + { + var perm = perms[i]; + + var result = perm.CheckPermission(message, commandName, moduleName); + + if (result == null) + { + continue; + } + else + { + permIndex = i; + return result.Value; + } + } + permIndex = -1; //defaut behaviour + return true; + } + + //null = not applicable + //true = applicable, allowed + //false = applicable, not allowed + public static bool? CheckPermission(this Permission perm, IUserMessage message, string commandName, string moduleName) + { + if (!((perm.SecondaryTarget == SecondaryPermissionType.Command && + perm.SecondaryTargetName.ToLowerInvariant() == commandName.ToLowerInvariant()) || + (perm.SecondaryTarget == SecondaryPermissionType.Module && + perm.SecondaryTargetName.ToLowerInvariant() == moduleName.ToLowerInvariant()) || + perm.SecondaryTarget == SecondaryPermissionType.AllModules)) + return null; + + var guildUser = message.Author as IGuildUser; + + switch (perm.PrimaryTarget) + { + case PrimaryPermissionType.User: + if (perm.PrimaryTargetId == message.Author.Id) + return perm.State; + break; + case PrimaryPermissionType.Channel: + if (perm.PrimaryTargetId == message.Channel.Id) + return perm.State; + break; + case PrimaryPermissionType.Role: + if (guildUser == null) + break; + if (guildUser.Roles.Any(r => r.Id == perm.PrimaryTargetId)) + return perm.State; + break; + case PrimaryPermissionType.Server: + if (guildUser == null) + break; + return perm.State; + } + return null; + } + + public static string GetCommand(this Permission perm, IGuild guild = null) + { + var com = ""; + switch (perm.PrimaryTarget) + { + case PrimaryPermissionType.User: + com += "u"; + break; + case PrimaryPermissionType.Channel: + com += "c"; + break; + case PrimaryPermissionType.Role: + com += "r"; + break; + case PrimaryPermissionType.Server: + com += "s"; + break; + } + + switch (perm.SecondaryTarget) + { + case SecondaryPermissionType.Module: + com += "m"; + break; + case SecondaryPermissionType.Command: + com += "c"; + break; + case SecondaryPermissionType.AllModules: + com = "a" + com + "m"; + break; + } + com += " " + (perm.SecondaryTargetName != "*" ? perm.SecondaryTargetName + " " : "") + (perm.State ? "enable" : "disable") + " "; + + switch (perm.PrimaryTarget) + { + case PrimaryPermissionType.User: + if (guild == null) + com += $"<@{perm.PrimaryTargetId}>"; + else + com += guild.GetUser(perm.PrimaryTargetId).ToString() ?? $"<@{perm.PrimaryTargetId}>"; + break; + case PrimaryPermissionType.Channel: + com += $"<#{perm.PrimaryTargetId}>"; + break; + case PrimaryPermissionType.Role: + if(guild == null) + com += $"<@&{perm.PrimaryTargetId}>"; + else + com += guild.GetRole(perm.PrimaryTargetId).ToString() ?? $"<@{perm.PrimaryTargetId}>"; + break; + case PrimaryPermissionType.Server: + break; + } + + return NadekoBot.ModulePrefixes[typeof(Permissions).Name] + com; + } + + public static void Prepend(this Permission perm, Permission toAdd) + { + perm = perm.GetRoot(); + + perm.Previous = toAdd; + toAdd.Next = perm; + } + + /* /this can't work if index < 0 and perm isn't roo + public static void Insert(this Permission perm, int index, Permission toAdd) + { + if (index < 0) + throw new IndexOutOfRangeException(); + + if (index == 0) + { + perm.Prepend(toAdd); + return; + } + + var atIndex = perm; + var i = 0; + while (i != index) + { + atIndex = atIndex.Next; + i++; + if (atIndex == null) + throw new IndexOutOfRangeException(); + } + var previous = atIndex.Previous; + + //connect right side + atIndex.Previous = toAdd; + toAdd.Next = atIndex; + + //connect left side + toAdd.Previous = previous; + previous.Next = toAdd; + } + */ + public static Permission RemoveAt(this Permission perm, int index) + { + if (index <= 0) //can't really remove at 0, that means deleting the element right now. Just use perm.Next if its 0 + throw new IndexOutOfRangeException(); + + var toRemove = perm; + var i = 0; + while (i != index) + { + toRemove = toRemove.Next; + i++; + if (toRemove == null) + throw new IndexOutOfRangeException(); + } + + toRemove.Previous.Next = toRemove.Next; + if (toRemove.Next != null) + toRemove.Next.Previous = toRemove.Previous; + return toRemove; + } + + public static Permission GetAt(this Permission perm, int index) + { + if (index < 0) + throw new IndexOutOfRangeException(); + var temp = perm; + while (index > 0) { temp = temp?.Next; index--; } + if (temp == null) + throw new IndexOutOfRangeException(); + return temp; + } + + public static int Count(this Permission perm) + { + var i = 1; + var temp = perm; + while ((temp = temp.Next) != null) { i++; } + return i; + } + + public static IEnumerable AsEnumerable(this Permission perm) + { + do yield return perm; + while ((perm = perm.Next) != null); + } + + public static Permission GetRoot(this Permission perm) + { + Permission toReturn; + do toReturn = perm; + while ((perm = perm.Previous) != null); + return toReturn; + } + } +} diff --git a/src/NadekoBot/Modules/Permissions/Permissions.cs b/src/NadekoBot/Modules/Permissions/Permissions.cs new file mode 100644 index 00000000..6b624257 --- /dev/null +++ b/src/NadekoBot/Modules/Permissions/Permissions.cs @@ -0,0 +1,630 @@ +using NadekoBot.Attributes; +using System; +using System.Linq; +using System.Threading.Tasks; +using Discord.Commands; +using NadekoBot.Services; +using Discord; +using NadekoBot.Services.Database.Models; +using System.Collections.Concurrent; + +namespace NadekoBot.Modules.Permissions +{ + [NadekoModule("Permissions", ";")] + public partial class Permissions : DiscordModule + { + public class PermissionCache + { + public string PermRole { get; set; } + public bool Verbose { get; set; } = true; + public Permission RootPermission { get; set; } + } + + //guildid, root permission + public static ConcurrentDictionary Cache; + + static Permissions() + { + using (var uow = DbHandler.UnitOfWork()) + { + Cache = new ConcurrentDictionary(uow.GuildConfigs + .PermissionsForAll() + .ToDictionary(k => k.GuildId, + v => new PermissionCache() + { + RootPermission = v.RootPermission, + Verbose = v.VerbosePermissions, + PermRole = v.PermissionRole + })); + } + } + + public Permissions(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Verbose(IUserMessage msg, PermissionAction action) + { + var channel = (ITextChannel)msg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + config.VerbosePermissions = action.Value; + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = Permission.GetDefaultRoot(), + Verbose = config.VerbosePermissions + }, (id, old) => { old.Verbose = config.VerbosePermissions; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + + await channel.SendMessageAsync("ℹ️ I will " + (action.Value ? "now" : "no longer") + " show permission warnings.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task PermRole(IUserMessage msg, [Remainder] IRole role = null) + { + var channel = (ITextChannel)msg.Channel; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + if (role == null) + { + await channel.SendMessageAsync($"ℹ️ Current permission role is **{config.PermissionRole}**.").ConfigureAwait(false); + return; + } + else { + config.PermissionRole = role.Name.Trim(); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = Permission.GetDefaultRoot(), + Verbose = config.VerbosePermissions + }, (id, old) => { old.PermRole = role.Name.Trim(); return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + } + + await channel.SendMessageAsync($"✅ Users now require **{role.Name}** role in order to edit permissions.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ListPerms(IUserMessage msg, int page = 1) + { + var channel = (ITextChannel)msg.Channel; + + if (page < 1 || page > 4) + return; + string toSend = ""; + using (var uow = DbHandler.UnitOfWork()) + { + var perms = uow.GuildConfigs.PermissionsFor(channel.Guild.Id).RootPermission; + var i = 1 + 20 * (page - 1); + toSend = Format.Code($"📄 Permissions page {page}") + "\n\n" + String.Join("\n", perms.AsEnumerable().Skip((page - 1) * 20).Take(20).Select(p => $"`{(i++)}.` {(p.Next == null ? Format.Bold(p.GetCommand(channel.Guild) + " [uneditable]") : (p.GetCommand(channel.Guild)))}")); + } + + if (string.IsNullOrWhiteSpace(toSend)) + await channel.SendMessageAsync("❗️`No permissions set.`").ConfigureAwait(false); + else + await channel.SendMessageAsync(toSend).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task RemovePerm(IUserMessage imsg, int index) + { + var channel = (ITextChannel)imsg.Channel; + index -= 1; + try + { + Permission p; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.PermissionsFor(channel.Guild.Id); + var perms = config.RootPermission; + if (index == perms.Count() - 1) + { + return; + } + else if (index == 0) + { + p = perms; + config.RootPermission = perms.Next; + } + else + { + p = perms.RemoveAt(index); + } + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + + using (var uow2 = DbHandler.UnitOfWork()) + { + uow2._context.Remove(p); + uow2._context.SaveChanges(); + } + + await channel.SendMessageAsync($"✅ {imsg.Author.Mention} removed permission **{p.GetCommand(channel.Guild)}** from position #{index + 1}.").ConfigureAwait(false); + } + catch (ArgumentOutOfRangeException) + { + await channel.SendMessageAsync("❗️`No command on that index found.`").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task MovePerm(IUserMessage imsg, int from, int to) + { + from -= 1; + to -= 1; + var channel = (ITextChannel)imsg.Channel; + if (!(from == to || from < 0 || to < 0)) + { + try + { + Permission fromPerm = null; + Permission toPerm = null; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.PermissionsFor(channel.Guild.Id); + var perms = config.RootPermission; + var root = perms; + var index = 0; + var fromFound = false; + var toFound = false; + while ((!toFound || !fromFound) && perms != null) + { + if (index == from) + { + fromPerm = perms; + fromFound = true; + } + if (index == to) + { + toPerm = perms; + toFound = true; + } + if (!toFound) + { + toPerm = perms; //In case of to > size + } + perms = perms.Next; + index++; + } + if (perms == null) + { + if (!fromFound) + { + await channel.SendMessageAsync($"❗️`Can't find permission at index `#{++from}`").ConfigureAwait(false); + return; + } + + if (!toFound) + { + await channel.SendMessageAsync($"❗️`Can't find permission at index `#{++to}`").ConfigureAwait(false); + return; + } + } + + //Change chain for from indx + var next = fromPerm.Next; + var pre = fromPerm.Previous; + if (pre != null) + pre.Next = next; + if (fromPerm.Next == null || toPerm.Next == null) + { + throw new IndexOutOfRangeException(); + } + next.Previous = pre; + if (from == 0) + { + root = next; + } + await uow.CompleteAsync().ConfigureAwait(false); + //Inserting + if (to > from) + { + fromPerm.Previous = toPerm; + fromPerm.Next = toPerm.Next; + + toPerm.Next.Previous = fromPerm; + toPerm.Next = fromPerm; + } + else + { + pre = toPerm.Previous; + + fromPerm.Next = toPerm; + fromPerm.Previous = pre; + + toPerm.Previous = fromPerm; + if (pre != null) + pre.Next = fromPerm; + } + + config.RootPermission = fromPerm.GetRoot(); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"✅ `Moved permission:` \"{fromPerm.GetCommand(channel.Guild)}\" `from #{++from} to #{++to}.`").ConfigureAwait(false); + return; + } + catch (Exception e) when (e is ArgumentOutOfRangeException || e is IndexOutOfRangeException) + { + } + } + await channel.SendMessageAsync("`Invalid index(es) specified.`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SrvrCmd(IUserMessage imsg, Command command, PermissionAction action) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.Command, + SecondaryTargetName = command.Text.ToLowerInvariant(), + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `{command.Text}` command on this server.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task SrvrMdl(IUserMessage imsg, Module module, PermissionAction action) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = module.Name.ToLowerInvariant(), + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of **`{module.Name}`** module on this server.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task UsrCmd(IUserMessage imsg, Command command, PermissionAction action, [Remainder] IGuildUser user) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.User, + PrimaryTargetId = user.Id, + SecondaryTarget = SecondaryPermissionType.Command, + SecondaryTargetName = command.Text.ToLowerInvariant(), + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `{command.Text}` command for `{user}` user.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task UsrMdl(IUserMessage imsg, Module module, PermissionAction action, [Remainder] IGuildUser user) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.User, + PrimaryTargetId = user.Id, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = module.Name.ToLowerInvariant(), + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `{module.Name}` module for `{user}` user.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task RoleCmd(IUserMessage imsg, Command command, PermissionAction action, [Remainder] IRole role) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Role, + PrimaryTargetId = role.Id, + SecondaryTarget = SecondaryPermissionType.Command, + SecondaryTargetName = command.Text.ToLowerInvariant(), + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `{command.Text}` command for `{role}` role.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task RoleMdl(IUserMessage imsg, Module module, PermissionAction action, [Remainder] IRole role) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Role, + PrimaryTargetId = role.Id, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = module.Name.ToLowerInvariant(), + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `{module.Name}` module for `{role}` role.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ChnlCmd(IUserMessage imsg, Command command, PermissionAction action, [Remainder] ITextChannel chnl) + { + var channel = (ITextChannel)imsg.Channel; + try + { + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Channel, + PrimaryTargetId = chnl.Id, + SecondaryTarget = SecondaryPermissionType.Command, + SecondaryTargetName = command.Text.ToLowerInvariant(), + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + } + catch (Exception ex) { + Console.WriteLine(ex); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `{command.Text}` command for `{chnl}` channel.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ChnlMdl(IUserMessage imsg, Module module, PermissionAction action, [Remainder] ITextChannel chnl) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Channel, + PrimaryTargetId = chnl.Id, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = module.Name.ToLowerInvariant(), + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `{module.Name}` module for `{chnl}` channel.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task AllChnlMdls(IUserMessage imsg, PermissionAction action, [Remainder] ITextChannel chnl) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Channel, + PrimaryTargetId = chnl.Id, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `ALL MODULES` for `{chnl}` channel.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task AllRoleMdls(IUserMessage imsg, PermissionAction action, [Remainder] IRole role) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Role, + PrimaryTargetId = role.Id, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `ALL MODULES` for `{role}` role.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task AllUsrMdls(IUserMessage imsg, PermissionAction action, [Remainder] IUser user) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.User, + PrimaryTargetId = user.Id, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = action.Value, + }; + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `ALL MODULES` for `{user}` user.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task AllSrvrMdls(IUserMessage imsg, PermissionAction action) + { + var channel = (ITextChannel)imsg.Channel; + + using (var uow = DbHandler.UnitOfWork()) + { + var newPerm = new Permission + { + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = action.Value, + }; + uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, newPerm); + + var allowUser = new Permission + { + PrimaryTarget = PrimaryPermissionType.User, + PrimaryTargetId = imsg.Author.Id, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = true, + }; + + var config = uow.GuildConfigs.SetNewRootPermission(channel.Guild.Id, allowUser); + Cache.AddOrUpdate(channel.Guild.Id, new PermissionCache() + { + PermRole = config.PermissionRole, + RootPermission = config.RootPermission, + Verbose = config.VerbosePermissions + }, (id, old) => { old.RootPermission = config.RootPermission; return old; }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync($"{(action.Value ? "✅ Allowed" : "🆗 Denied")} usage of `ALL MODULES` on this server.").ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs new file mode 100644 index 00000000..8571846b --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/AnimeSearchCommands.cs @@ -0,0 +1,188 @@ +using Discord; +using Discord.API; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Searches.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class AnimeSearchCommands + { + private Logger _log; + + private string anilistToken { get; set; } + private DateTime lastRefresh { get; set; } + + public AnimeSearchCommands() + { + _log = LogManager.GetCurrentClassLogger(); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Anime(IUserMessage umsg, [Remainder] string query) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(query)) + return; + + var animeData = await GetAnimeData(query).ConfigureAwait(false); + + var embed = new Discord.API.Embed() + { + Description = animeData.Synopsis, + Title = animeData.title_english, + Url = animeData.Link, + Image = new Discord.API.EmbedImage() { + Url = animeData.image_url_lge + }, + Fields = new[] { + new Discord.API.EmbedField() { + Inline = true, + Name = "Episodes", + Value = animeData.total_episodes.ToString() + }, + new Discord.API.EmbedField() { + Inline = true, + Name = "Status", + Value = animeData.AiringStatus.ToString() + }, + new Discord.API.EmbedField() { + Inline = true, + Name = "Genres", + Value = String.Join(", ", animeData.Genres) + } + }, + Color = NadekoBot.OkColor + }; + await channel.EmbedAsync(embed).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Manga(IUserMessage umsg, [Remainder] string query) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(query)) + return; + + var animeData = await GetMangaData(query).ConfigureAwait(false); + + var embed = new Discord.API.Embed() + { + Description = animeData.Synopsis, + Title = animeData.title_english, + Url = animeData.Link, + Image = new Discord.API.EmbedImage() + { + Url = animeData.image_url_lge + }, + Fields = new[] { + new Discord.API.EmbedField() { + Inline = true, + Name = "Chapters", + Value = animeData.total_chapters.ToString() + }, + new Discord.API.EmbedField() { + Inline = true, + Name = "Status", + Value = animeData.publishing_status.ToString() + }, + new Discord.API.EmbedField() { + Inline = true, + Name = "Genres", + Value = String.Join(", ", animeData.Genres) + } + }, + Color = NadekoBot.OkColor + }; + + await channel.EmbedAsync(embed).ConfigureAwait(false); + } + + private async Task GetAnimeData(string query) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + try + { + await RefreshAnilistToken().ConfigureAwait(false); + + var link = "http://anilist.co/api/anime/search/" + Uri.EscapeUriString(query); + using (var http = new HttpClient()) + { + var res = await http.GetStringAsync("http://anilist.co/api/anime/search/" + Uri.EscapeUriString(query) + $"?access_token={anilistToken}").ConfigureAwait(false); + var smallObj = JArray.Parse(res)[0]; + var aniData = await http.GetStringAsync("http://anilist.co/api/anime/" + smallObj["id"] + $"?access_token={anilistToken}").ConfigureAwait(false); + + return await Task.Run(() => { try { return JsonConvert.DeserializeObject(aniData); } catch { return null; } }).ConfigureAwait(false); + } + } + catch (Exception ex) { + _log.Warn(ex, "Failed anime search for {0}", query); + return null; + } + } + + private async Task RefreshAnilistToken() + { + if (DateTime.Now - lastRefresh > TimeSpan.FromMinutes(29)) + lastRefresh = DateTime.Now; + else + { + return; + } + var headers = new Dictionary { + {"grant_type", "client_credentials"}, + {"client_id", "kwoth-w0ki9"}, + {"client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z"}, + }; + using (var http = new HttpClient()) + { + http.AddFakeHeaders(); + var formContent = new FormUrlEncodedContent(headers); + var response = await http.PostAsync("http://anilist.co/api/auth/access_token", formContent).ConfigureAwait(false); + var stringContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + anilistToken = JObject.Parse(stringContent)["access_token"].ToString(); + } + + } + + private async Task GetMangaData(string query) + { + if (string.IsNullOrWhiteSpace(query)) + throw new ArgumentNullException(nameof(query)); + try + { + await RefreshAnilistToken().ConfigureAwait(false); + using (var http = new HttpClient()) + { + var res = await http.GetStringAsync("http://anilist.co/api/manga/search/" + Uri.EscapeUriString(query) + $"?access_token={anilistToken}").ConfigureAwait(false); + var smallObj = JArray.Parse(res)[0]; + var aniData = await http.GetStringAsync("http://anilist.co/api/manga/" + smallObj["id"] + $"?access_token={anilistToken}").ConfigureAwait(false); + + return await Task.Run(() => { try { return JsonConvert.DeserializeObject(aniData); } catch { return null; } }).ConfigureAwait(false); + } + } + catch (Exception ex) + { + _log.Warn(ex, "Failed anime search for {0}", query); + return null; + } + } + } + } +} \ No newline at end of file diff --git a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs b/src/NadekoBot/Modules/Searches/Commands/GoogleTranslator.cs similarity index 67% rename from NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs rename to src/NadekoBot/Modules/Searches/Commands/GoogleTranslator.cs index 4bc8f384..be415465 100644 --- a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs +++ b/src/NadekoBot/Modules/Searches/Commands/GoogleTranslator.cs @@ -1,94 +1,23 @@ -// Copyright (c) 2015 Ravi Bhavnani -// License: Code Project Open License -// http://www.codeproject.com/info/cpol10.aspx - -using Newtonsoft.Json.Linq; -using System; +using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Threading.Tasks; -using System.Web; -namespace NadekoBot.Modules.Translator.Helpers +namespace NadekoBot.Modules.Searches { - /// - /// Translates text using Google's online language tools. - /// public class GoogleTranslator { - #region Properties + private static GoogleTranslator _instance; + public static GoogleTranslator Instance = _instance ?? (_instance = new GoogleTranslator()); - /// - /// Gets the supported languages. - /// - public static IEnumerable Languages { - get { - GoogleTranslator.EnsureInitialized(); - return GoogleTranslator._languageModeMap.Keys.OrderBy(p => p); - } - } - #endregion + public IEnumerable Languages => _languageDictionary.Keys.OrderBy(x => x); + private Dictionary _languageDictionary; - #region Public methods - - /// - /// Translates the specified source text. - /// - /// The source text. - /// The source language. - /// The target language. - /// The translation. - public async Task Translate - (string sourceText, - string sourceLanguage, - string targetLanguage) - { - // Initialize - DateTime tmStart = DateTime.Now; - string text = string.Empty; - - - // Download translation - string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", - GoogleTranslator.LanguageEnumToIdentifier(sourceLanguage), - GoogleTranslator.LanguageEnumToIdentifier(targetLanguage), - HttpUtility.UrlEncode(sourceText)); - using (HttpClient http = new HttpClient()) - { - http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); - text = await http.GetStringAsync(url).ConfigureAwait(false); - } - - return (string.Concat(JArray.Parse(text)[0].Select(x => x[0]))); - } - - #endregion - - #region Private methods - - /// - /// Converts a language to its identifier. - /// - /// The language." - /// The identifier or if none. - private static string LanguageEnumToIdentifier - (string language) - { - string mode = string.Empty; - GoogleTranslator.EnsureInitialized(); - GoogleTranslator._languageModeMap.TryGetValue(language, out mode); - return mode; - } - - /// - /// Ensures the translator has been initialized. - /// - public static void EnsureInitialized() - { - if (GoogleTranslator._languageModeMap == null) - { - GoogleTranslator._languageModeMap = new Dictionary() { + static GoogleTranslator() { } + private GoogleTranslator() { + _languageDictionary = new Dictionary() { { "afrikaans", "af"}, { "albanian", "sq"}, { "arabic", "ar"}, @@ -165,8 +94,8 @@ namespace NadekoBot.Modules.Translator.Helpers { "bn", "bn"}, { "bg", "bg"}, { "ca", "ca"}, - { "zh-TW", "zh-TW"}, - { "zh-CN", "zh-CN"}, + { "zh-tw", "zh-TW"}, + { "zh-cn", "zh-CN"}, { "hr", "hr"}, { "cs", "cs"}, { "da", "da"}, @@ -220,18 +149,30 @@ namespace NadekoBot.Modules.Translator.Helpers { "cy", "cy"}, { "yi", "yi"}, }; - } } - #endregion + public async Task Translate(string sourceText, string sourceLanguage, string targetLanguage) + { + string text = string.Empty; - #region Fields + string url = string.Format("https://translate.googleapis.com/translate_a/single?client=gtx&sl={0}&tl={1}&dt=t&q={2}", + ConvertToLanguageCode(sourceLanguage), + ConvertToLanguageCode(targetLanguage), + WebUtility.UrlEncode(sourceText)); + using (HttpClient http = new HttpClient()) + { + http.DefaultRequestHeaders.Add("user-agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); + text = await http.GetStringAsync(url).ConfigureAwait(false); + } - /// - /// The language to translation mode map. - /// - public static Dictionary _languageModeMap; + return (string.Concat(JArray.Parse(text)[0].Select(x => x[0]))); + } - #endregion + private string ConvertToLanguageCode(string language) + { + string mode = string.Empty; + _languageDictionary.TryGetValue(language, out mode); + return mode; + } } } diff --git a/src/NadekoBot/Modules/Searches/Commands/JokeCommands.cs b/src/NadekoBot/Modules/Searches/Commands/JokeCommands.cs new file mode 100644 index 00000000..98695f6f --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/JokeCommands.cs @@ -0,0 +1,104 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Modules.Searches.Models; +using NadekoBot.Services; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NLog; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class JokeCommands + { + private List wowJokes = new List(); + private List magicItems; + private Logger _log; + + public JokeCommands() + { + _log = LogManager.GetCurrentClassLogger(); + if (File.Exists("data/wowjokes.json")) + { + wowJokes = JsonConvert.DeserializeObject>(File.ReadAllText("data/wowjokes.json")); + } + else + _log.Warn("data/wowjokes.json is missing. WOW Jokes are not loaded."); + + if (File.Exists("data/magicitems.json")) + { + magicItems = JsonConvert.DeserializeObject>(File.ReadAllText("data/magicitems.json")); + } + else + _log.Warn("data/magicitems.json is missing. Magic items are not loaded."); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Yomama(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + using (var http = new HttpClient()) + { + var response = await http.GetStringAsync("http://api.yomomma.info/").ConfigureAwait(false); + await channel.SendMessageAsync("`" + JObject.Parse(response)["joke"].ToString() + "` 😆").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Randjoke(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + using (var http = new HttpClient()) + { + var response = await http.GetStringAsync("http://tambal.azurewebsites.net/joke/random").ConfigureAwait(false); + await channel.SendMessageAsync("`" + JObject.Parse(response)["joke"].ToString() + "` 😆").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ChuckNorris(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + using (var http = new HttpClient()) + { + var response = await http.GetStringAsync("http://api.icndb.com/jokes/random/").ConfigureAwait(false); + await channel.SendMessageAsync("`" + JObject.Parse(response)["value"]["joke"].ToString() + "` 😆").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WowJoke(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + if (!wowJokes.Any()) + { + } + await channel.SendMessageAsync(wowJokes[new NadekoRandom().Next(0, wowJokes.Count)].ToString()); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task MagicItem(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + var rng = new NadekoRandom(); + var item = magicItems[rng.Next(0, magicItems.Count)].ToString(); + + await channel.SendMessageAsync(item).ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs b/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs new file mode 100644 index 00000000..b915a835 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/LoLCommands.cs @@ -0,0 +1,391 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +//todo drawing +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + private class ChampionNameComparer : IEqualityComparer + { + public bool Equals(JToken a, JToken b) => a["name"].ToString() == b["name"].ToString(); + + public int GetHashCode(JToken obj) => + obj["name"].GetHashCode(); + } + + private string[] trashTalk { get; } = { "Better ban your counters. You are going to carry the game anyway.", + "Go with the flow. Don't think. Just ban one of these.", + "DONT READ BELOW! Ban Urgot mid OP 100%. Im smurf Diamond 1.", + "Ask your teammates what would they like to play, and ban that.", + "If you consider playing teemo, do it. If you consider teemo, you deserve him.", + "Doesn't matter what you ban really. Enemy will ban your main and you will lose." }; + + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Lolban(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + + + var showCount = 8; + //http://api.champion.gg/stats/champs/mostBanned?api_key=YOUR_API_TOKEN&page=1&limit=2 + try + { + using (var http = new HttpClient()) + { + var data = JObject.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" + + $"api_key={NadekoBot.Credentials.LoLApiKey}&page=1&" + + $"limit={showCount}") + .ConfigureAwait(false))["data"] as JArray; + var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList(); + var sb = new StringBuilder(); + sb.AppendLine($"**Showing {dataList.Count} top banned champions.**"); + sb.AppendLine($"`{trashTalk[new NadekoRandom().Next(0, trashTalk.Length)]}`"); + for (var i = 0; i < dataList.Count; i++) + { + if (i % 2 == 0 && i != 0) + sb.AppendLine(); + sb.Append($"`{i + 1}.` **{dataList[i]["name"]}** {dataList[i]["general"]["banRate"]}% "); + //sb.AppendLine($" ({dataList[i]["general"]["banRate"]}%)"); + } + + await channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false); + } + } + catch (Exception) + { + await channel.SendMessageAsync($":anger: `Something went wrong.`").ConfigureAwait(false); + } + } + } +} + +// private class CachedChampion +// { +// public System.IO.Stream ImageStream { get; set; } +// public DateTime AddedAt { get; set; } +// public string Name { get; set; } +// } + +// +// private static Dictionary CachedChampionImages = new Dictionary(); + +// private System.Timers.Timer clearTimer { get; } = new System.Timers.Timer(); +// public LoLCommands(DiscordModule module) : base(module) +// { +// clearTimer.Interval = new TimeSpan(0, 10, 0).TotalMilliseconds; +// clearTimer.Start(); +// clearTimer.Elapsed += (s, e) => +// { +// try +// { +// CachedChampionImages = CachedChampionImages +// .Where(kvp => DateTime.Now - kvp.Value.AddedAt > new TimeSpan(1, 0, 0)) +// .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); +// } +// catch { } +// }; +// } + +// public Func DoFunc() +// { +// throw new NotImplementedException(); +// } + +// private class MatchupModel +// { +// public int Games { get; set; } +// public float WinRate { get; set; } +// [Newtonsoft.Json.JsonProperty("key")] +// public string Name { get; set; } +// public float StatScore { get; set; } +// } + +// public override void Init(CommandGroupBuilder cgb) +// { +// cgb.CreateCommand(Module.Prefix + "lolchamp") +// .Description($"Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. |`{Prefix}lolchamp Riven` or `{Prefix}lolchamp Annie sup`") +// .Parameter("champ", ParameterType.Required) +// .Parameter("position", ParameterType.Unparsed) +// .Do(async e => +// { +// try +// { +// //get role +// var role = ResolvePos(position); +// var resolvedRole = role; +// var name = champ.Replace(" ", "").ToLower(); +// CachedChampion champ = null; + +// if (CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ)) +// if (champ != null) +// { +// champ.ImageStream.Position = 0; +// await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); +// return; +// } +// var allData = JArray.Parse(await Classes.http.GetStringAsync($"http://api.champion.gg/champion/{name}?api_key={NadekoBot.Credentials.LOLAPIKey}").ConfigureAwait(false)); +// JToken data = null; +// if (role != null) +// { +// for (var i = 0; i < allData.Count; i++) +// { +// if (allData[i]["role"].ToString().Equals(role)) +// { +// data = allData[i]; +// break; +// } +// } +// if (data == null) +// { +// await channel.SendMessageAsync("💢 Data for that role does not exist.").ConfigureAwait(false); +// return; +// } +// } +// else +// { +// data = allData[0]; +// role = allData[0]["role"].ToString(); +// resolvedRole = ResolvePos(role); +// } +// if (CachedChampionImages.TryGetValue(name + "_" + resolvedRole, out champ)) +// if (champ != null) +// { +// champ.ImageStream.Position = 0; +// await e.Channel.SendFile("champ.png", champ.ImageStream).ConfigureAwait(false); +// return; +// } +// //name = data["title"].ToString(); +// // get all possible roles, and "select" the shown one +// var roles = new string[allData.Count]; +// for (var i = 0; i < allData.Count; i++) +// { +// roles[i] = allData[i]["role"].ToString(); +// if (roles[i] == role) +// roles[i] = ">" + roles[i] + "<"; +// } +// var general = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/" + +// $"champs/{name}?api_key={NadekoBot.Credentials.LOLAPIKey}") +// .ConfigureAwait(false)) +// .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"]; +// if (general == null) +// { +// Console.WriteLine("General is null."); +// return; +// } +// //get build data for this role +// var buildData = data["items"]["mostGames"]["items"]; +// var items = new string[6]; +// for (var i = 0; i < 6; i++) +// { +// items[i] = buildData[i]["id"].ToString(); +// } + +// //get matchup data to show counters and countered champions +// var matchupDataIE = data["matchups"].ToObject>(); + +// var matchupData = matchupDataIE.OrderBy(m => m.StatScore).ToArray(); + +// var countered = new[] { matchupData[0].Name, matchupData[1].Name, matchupData[2].Name }; +// var counters = new[] { matchupData[matchupData.Length - 1].Name, matchupData[matchupData.Length - 2].Name, matchupData[matchupData.Length - 3].Name }; + +// //get runes data +// var runesJArray = data["runes"]["mostGames"]["runes"] as JArray; +// var runes = string.Join("\n", runesJArray.OrderBy(jt => int.Parse(jt["number"].ToString())).Select(jt => jt["number"].ToString() + "x" + jt["name"])); + +// // get masteries data + +// var masteries = (data["masteries"]["mostGames"]["masteries"] as JArray); + +// //get skill order data + +// var orderArr = (data["skills"]["mostGames"]["order"] as JArray); + +// var img = Image.FromFile("data/lol/bg.png"); +// using (var g = Graphics.FromImage(img)) +// { +// g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; +// //g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy; +// const int margin = 5; +// const int imageSize = 75; +// var normalFont = new Font("Monaco", 8, FontStyle.Regular); +// var smallFont = new Font("Monaco", 7, FontStyle.Regular); +// //draw champ image +// var champName = data["key"].ToString().Replace(" ", ""); + +// g.DrawImage(GetImage(champName), new Rectangle(margin, margin, imageSize, imageSize)); +// //draw champ name +// if (champName == "MonkeyKing") +// champName = "Wukong"; +// g.DrawString($"{champName}", new Font("Times New Roman", 24, FontStyle.Regular), Brushes.WhiteSmoke, margin + imageSize + margin, margin); +// //draw champ surname + +// //draw skill order +// if (orderArr.Count != 0) +// { +// float orderFormula = 120 / orderArr.Count; +// const float orderVerticalSpacing = 10; +// for (var i = 0; i < orderArr.Count; i++) +// { +// var orderX = margin + margin + imageSize + orderFormula * i + i; +// float orderY = margin + 35; +// var spellName = orderArr[i].ToString().ToLowerInvariant(); + +// switch (spellName) +// { +// case "w": +// orderY += orderVerticalSpacing; +// break; +// case "e": +// orderY += orderVerticalSpacing * 2; +// break; +// case "r": +// orderY += orderVerticalSpacing * 3; +// break; +// default: +// break; +// } + +// g.DrawString(spellName.ToUpperInvariant(), new Font("Monaco", 7), Brushes.LimeGreen, orderX, orderY); +// } +// } +// //draw roles +// g.DrawString("Roles: " + string.Join(", ", roles), normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin); + +// //draw average stats +// g.DrawString( +//$@" Average Stats + +//Kills: {general["kills"]} CS: {general["minionsKilled"]} +//Deaths: {general["deaths"]} Win: {general["winPercent"]}% +//Assists: {general["assists"]} Ban: {general["banRate"]}% +//", normalFont, Brushes.WhiteSmoke, img.Width - 150, margin); +// //draw masteries +// g.DrawString($"Masteries: {string.Join(" / ", masteries?.Select(jt => jt["total"]))}", normalFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 20); +// //draw runes +// g.DrawString($"{runes}", smallFont, Brushes.WhiteSmoke, margin, margin + imageSize + margin + 40); +// //draw counters +// g.DrawString($"Best against", smallFont, Brushes.WhiteSmoke, margin, img.Height - imageSize + margin); +// var smallImgSize = 50; + +// for (var i = 0; i < counters.Length; i++) +// { +// g.DrawImage(GetImage(counters[i]), +// new Rectangle(i * (smallImgSize + margin) + margin, img.Height - smallImgSize - margin, +// smallImgSize, +// smallImgSize)); +// } +// //draw countered by +// g.DrawString($"Worst against", smallFont, Brushes.WhiteSmoke, img.Width - 3 * (smallImgSize + margin), img.Height - imageSize + margin); + +// for (var i = 0; i < countered.Length; i++) +// { +// var j = countered.Length - i; +// g.DrawImage(GetImage(countered[i]), +// new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), img.Height - smallImgSize - margin, +// smallImgSize, +// smallImgSize)); +// } +// //draw item build +// g.DrawString("Popular build", normalFont, Brushes.WhiteSmoke, img.Width - (3 * (smallImgSize + margin) + margin), 77); + +// for (var i = 0; i < 6; i++) +// { +// var inverseI = 5 - i; +// var j = inverseI % 3 + 1; +// var k = inverseI / 3; +// g.DrawImage(GetImage(items[i], GetImageType.Item), +// new Rectangle(img.Width - (j * (smallImgSize + margin) + margin), 92 + k * (smallImgSize + margin), +// smallImgSize, +// smallImgSize)); +// } +// } +// var cachedChamp = new CachedChampion { AddedAt = DateTime.Now, ImageStream = img.ToStream(System.Drawing.Imaging.ImageFormat.Png), Name = name.ToLower() + "_" + resolvedRole }; +// CachedChampionImages.Add(cachedChamp.Name, cachedChamp); +// await e.Channel.SendFile(data["title"] + "_stats.png", cachedChamp.ImageStream).ConfigureAwait(false); +// } +// catch (Exception ex) +// { +// Console.WriteLine(ex); +// await channel.SendMessageAsync("💢 Failed retreiving data for that champion.").ConfigureAwait(false); +// } +// }); +// } + +// private enum GetImageType +// { +// Champion, +// Item +// } +// private static Image GetImage(string id, GetImageType imageType = GetImageType.Champion) +// { +// try +// { +// switch (imageType) +// { +// case GetImageType.Champion: +// return Image.FromFile($"data/lol/champions/{id}.png"); +// case GetImageType.Item: +// default: +// return Image.FromFile($"data/lol/items/{id}.png"); +// } +// } +// catch (Exception) +// { +// return Image.FromFile("data/lol/_ERROR.png"); +// } +// } + +// private static string ResolvePos(string pos) +// { +// if (string.IsNullOrWhiteSpace(pos)) +// return null; +// switch (pos.ToLowerInvariant()) +// { +// case "m": +// case "mid": +// case "midorfeed": +// case "midd": +// case "middle": +// return "Middle"; +// case "top": +// case "topp": +// case "t": +// case "toporfeed": +// return "Top"; +// case "j": +// case "jun": +// case "jungl": +// case "jungle": +// return "Jungle"; +// case "a": +// case "ad": +// case "adc": +// case "carry": +// case "ad carry": +// case "adcarry": +// case "c": +// return "ADC"; +// case "s": +// case "sup": +// case "supp": +// case "support": +// return "Support"; +// default: +// return pos; +// } +// } +// } +//} diff --git a/src/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs b/src/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs new file mode 100644 index 00000000..6cd84286 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/MemegenCommands.cs @@ -0,0 +1,56 @@ +using Discord.Commands; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Discord; +using System.Threading.Tasks; +using NadekoBot.Attributes; +using System.Net.Http; +using NadekoBot.Extensions; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Memelist(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + HttpClientHandler handler = new HttpClientHandler(); + + handler.AllowAutoRedirect = false; + + using (var http = new HttpClient(handler)) + { + http.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + string rawJson = ""; + try + { + rawJson = await http.GetStringAsync("https://memegen.link/api/templates/").ConfigureAwait(false); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + var data = JsonConvert.DeserializeObject>(rawJson) + .Select(kvp => Path.GetFileName(kvp.Value)); + + await channel.SendTableAsync(data, x => $"{x,-17}", 3); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Memegen(IUserMessage umsg, string meme, string topText, string botText) + { + var channel = (ITextChannel)umsg.Channel; + + var top = Uri.EscapeDataString(topText.Replace(' ', '-')); + var bot = Uri.EscapeDataString(botText.Replace(' ', '-')); + await channel.SendMessageAsync($"http://memegen.link/{meme}/{top}/{bot}.jpg"); + } + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/AnimeResult.cs b/src/NadekoBot/Modules/Searches/Commands/Models/AnimeResult.cs new file mode 100644 index 00000000..3d08da6e --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/Models/AnimeResult.cs @@ -0,0 +1,19 @@ +using NadekoBot.Extensions; +using System.Globalization; + +namespace NadekoBot.Modules.Searches.Models +{ + public class AnimeResult + { + public int id; + public string AiringStatus => airing_status.ToTitleCase(); + public string airing_status; + public string title_english; + public int total_episodes; + public string description; + public string image_url_lge; + public string[] Genres; + public string Link => "http://anilist.co/anime/" + id; + public string Synopsis => description?.Substring(0, description.Length > 500 ? 500 : description.Length) + "..."; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/ImdbMovie.cs b/src/NadekoBot/Modules/Searches/Commands/Models/ImdbMovie.cs new file mode 100644 index 00000000..318eb6dc --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/Models/ImdbMovie.cs @@ -0,0 +1,33 @@ +using NadekoBot.Extensions; +using System.Collections.Generic; +using System.Net; + +namespace NadekoBot.Modules.Searches.Models +{ + public class ImdbMovie + { + public bool Status { get; set; } + public string Id { get; set; } + public string Title { get; set; } + public string OriginalTitle { get; set; } + public string Year { get; set; } + public string Rating { get; set; } + public string Plot { get; set; } + public string Poster { get; set; } + public List Genres { get; set; } + public string ImdbURL { get; set; } + + public Dictionary Aka { get; set; } + + public override string ToString() => +$@"`Title:` {WebUtility.HtmlDecode(Title)} {(string.IsNullOrEmpty(OriginalTitle) ? "" : $"({OriginalTitle})")} +`Year:` {Year} +`Rating:` {Rating} +`Genre:` {GenresAsString} +`Link:` <{ImdbURL}> +`Plot:` {System.Net.WebUtility.HtmlDecode(Plot.TrimTo(500))} +`Poster:` " + NadekoBot.Google.ShortenUrl(Poster).GetAwaiter().GetResult(); + public string GenresAsString => + string.Join(", ", Genres); + } +} \ No newline at end of file diff --git a/NadekoBot/_Models/JSONModels/MagicItem.cs b/src/NadekoBot/Modules/Searches/Commands/Models/MagicItem.cs similarity index 57% rename from NadekoBot/_Models/JSONModels/MagicItem.cs rename to src/NadekoBot/Modules/Searches/Commands/Models/MagicItem.cs index 1ef3db1b..53397e8e 100644 --- a/NadekoBot/_Models/JSONModels/MagicItem.cs +++ b/src/NadekoBot/Modules/Searches/Commands/Models/MagicItem.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace NadekoBot.Classes.JSONModels +namespace NadekoBot.Modules.Searches.Models { class MagicItem { diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/MangaResult.cs b/src/NadekoBot/Modules/Searches/Commands/Models/MangaResult.cs new file mode 100644 index 00000000..5e262209 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/Models/MangaResult.cs @@ -0,0 +1,16 @@ +namespace NadekoBot.Modules.Searches.Models +{ + public class MangaResult + { + public int id; + public string publishing_status; + public string image_url_lge; + public string title_english; + public int total_chapters; + public int total_volumes; + public string description; + public string[] Genres; + public string Link => "http://anilist.co/manga/" + id; + public string Synopsis => description?.Substring(0, description.Length > 500 ? 500 : description.Length) + "..."; + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/SearchPokemon.cs b/src/NadekoBot/Modules/Searches/Commands/Models/SearchPokemon.cs new file mode 100644 index 00000000..be5411d1 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/Models/SearchPokemon.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; + +namespace NadekoBot.Modules.Searches.Models +{ + public class SearchPokemon + { + public class GenderRatioClass + { + public float M { get; set; } + public float F { get; set; } + } + public class BaseStatsClass + { + public int HP { get; set; } + public int ATK { get; set; } + public int DEF { get; set; } + public int SPA { get; set; } + public int SPD { get; set; } + public int SPE { get; set; } + + public override string ToString() => $@" + **HP:** {HP,-4} **ATK:** {ATK,-4} **DEF:** {DEF,-4} + **SPA:** {SPA,-4} **SPD:** {SPD,-4} **SPE:** {SPE,-4}"; + } + public int Id { get; set; } + public string Species { get; set; } + public string[] Types { get; set; } + public GenderRatioClass GenderRatio { get; set; } + public BaseStatsClass BaseStats { get; set; } + public Dictionary Abilities { get; set; } + public float HeightM { get; set; } + public float WeightKg { get; set; } + public string Color { get; set; } + public string[] Evos { get; set; } + public string[] EggGroups { get; set; } + + public override string ToString() => $@"`Name:` {Species} +`Types:` {string.Join(", ", Types)} +`Stats:` {BaseStats} +`Height:` {HeightM,4}m `Weight:` {WeightKg}kg +`Abilities:` {string.Join(", ", Abilities.Values)}"; + + } + + public class SearchPokemonAbility + { + public string Desc { get; set; } + public string Name { get; set; } + public float Rating { get; set; } + + public override string ToString() => $@"`Name:` : {Name} +`Rating:` {Rating} +`Description:` {Desc}"; + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/WikipediaApiModel.cs b/src/NadekoBot/Modules/Searches/Commands/Models/WikipediaApiModel.cs new file mode 100644 index 00000000..ed961a2a --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/Models/WikipediaApiModel.cs @@ -0,0 +1,18 @@ +namespace NadekoBot.Modules.Searches.Models +{ + public class WikipediaApiModel + { + public WikipediaQuery Query { get; set; } + + public class WikipediaQuery + { + public WikipediaPage[] Pages { get; set; } + + public class WikipediaPage + { + public bool Missing { get; set; } = false; + public string FullUrl { get; set; } + } + } + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/Models/WoWJoke.cs b/src/NadekoBot/Modules/Searches/Commands/Models/WoWJoke.cs new file mode 100644 index 00000000..b353abd9 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/Models/WoWJoke.cs @@ -0,0 +1,9 @@ +namespace NadekoBot.Modules.Searches.Models +{ + public class WoWJoke + { + public string Question { get; set; } + public string Answer { get; set; } + public override string ToString() => $"`{Question}`\n\n**{Answer}**"; + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs b/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs new file mode 100644 index 00000000..a4fffa9d --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/OMDB/OmdbProvider.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches.Commands.OMDB +{ + public static class OmdbProvider + { + private const string queryUrl = "http://www.omdbapi.com/?t={0}&y=&plot=full&r=json"; + + public static async Task FindMovie(string name) + { + using (var http = new HttpClient()) + { + var res = await http.GetStringAsync(String.Format(queryUrl,name.Trim().Replace(' ','+'))).ConfigureAwait(false); + var movie = JsonConvert.DeserializeObject(res); + + movie.Poster = await NadekoBot.Google.ShortenUrl(movie.Poster); + return movie; + } + } + } + + public class OmdbMovie + { + public string Title { get; set; } + public string Year { get; set; } + public string ImdbRating { get; set; } + public string ImdbId { get; set; } + public string Genre { get; set; } + public string Plot { get; set; } + public string Poster { get; set; } + + public override string ToString() => +$@"`Title:` {Title} +`Year:` {Year} +`Rating:` {ImdbRating} +`Genre:` {Genre} +`Link:` http://www.imdb.com/title/{ImdbId}/ +`Plot:` {Plot}"; + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs b/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs new file mode 100644 index 00000000..e3d663a5 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/OsuCommands.cs @@ -0,0 +1,276 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using Newtonsoft.Json.Linq; +using NLog; +using System; +using System.Globalization; +using System.IO; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class OsuCommands + { + private Logger _log; + + public OsuCommands() + { + _log = LogManager.GetCurrentClassLogger(); + } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Osu(IUserMessage umsg, string usr, [Remainder] string mode = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(usr)) + return; + + using (HttpClient http = new HttpClient()) + { + try + { + var m = 0; + if (!string.IsNullOrWhiteSpace(mode)) + { + m = ResolveGameMode(mode); + } + http.AddFakeHeaders(); + var res = await http.GetStreamAsync(new Uri($"http://lemmmy.pw/osusig/sig.php?uname={ usr }&flagshadow&xpbar&xpbarhex&pp=2&mode={m}")).ConfigureAwait(false); + + MemoryStream ms = new MemoryStream(); + res.CopyTo(ms); + ms.Position = 0; + await channel.SendFileAsync(ms, $"{usr}.png", $"🎧 **Profile Link: **https://osu.ppy.sh/u/{Uri.EscapeDataString(usr)}\n`Image provided by https://lemmmy.pw/osusig`").ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync("💢 Failed retrieving osu signature :\\").ConfigureAwait(false); + _log.Warn(ex, "Osu command failed"); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Osub(IUserMessage umsg, [Remainder] string map) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.OsuApiKey)) + { + await channel.SendMessageAsync("💢 An osu! API key is required.").ConfigureAwait(false); + return; + } + + if (string.IsNullOrWhiteSpace(map)) + return; + + try + { + using (var http = new HttpClient()) + { + var mapId = ResolveMap(map); + var reqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Credentials.OsuApiKey}&{mapId}"; + var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false))[0]; + var sb = new System.Text.StringBuilder(); + var starRating = Math.Round(Double.Parse($"{obj["difficultyrating"]}", CultureInfo.InvariantCulture), 2); + var time = TimeSpan.FromSeconds(Double.Parse($"{obj["total_length"]}")).ToString(@"mm\:ss"); + sb.AppendLine($"{obj["artist"]} - {obj["title"]}, mapped by {obj["creator"]}. https://osu.ppy.sh/s/{obj["beatmapset_id"]}"); + sb.AppendLine($"{starRating} stars, {obj["bpm"]} BPM | AR{obj["diff_approach"]}, CS{obj["diff_size"]}, OD{obj["diff_overall"]} | Length: {time}"); + await channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false); + } + } + catch (Exception ex) + { + await channel.SendMessageAsync("Something went wrong."); + _log.Warn(ex, "Osub command failed"); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Osu5(IUserMessage umsg, string user, [Remainder] string mode = null) + { + var channel = (ITextChannel)umsg.Channel; + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.OsuApiKey)) + { + await channel.SendMessageAsync("💢 An osu! API key is required.").ConfigureAwait(false); + return; + } + + if (string.IsNullOrWhiteSpace(user)) + { + await channel.SendMessageAsync("💢 Please provide a username.").ConfigureAwait(false); + return; + } + using (var http = new HttpClient()) + { + try + { + var m = 0; + if (!string.IsNullOrWhiteSpace(mode)) + { + m = ResolveGameMode(mode); + } + + var reqString = $"https://osu.ppy.sh/api/get_user_best?k={NadekoBot.Credentials.OsuApiKey}&u={Uri.EscapeDataString(user)}&type=string&limit=5&m={m}"; + var obj = JArray.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false)); + var sb = new System.Text.StringBuilder($"`Top 5 plays for {user}:`\n```xl" + Environment.NewLine); + foreach (var item in obj) + { + var mapReqString = $"https://osu.ppy.sh/api/get_beatmaps?k={NadekoBot.Credentials.OsuApiKey}&b={item["beatmap_id"]}"; + var map = JArray.Parse(await http.GetStringAsync(mapReqString).ConfigureAwait(false))[0]; + var pp = Math.Round(Double.Parse($"{item["pp"]}", CultureInfo.InvariantCulture), 2); + var acc = CalculateAcc(item, m); + var mods = ResolveMods(Int32.Parse($"{item["enabled_mods"]}")); + if (mods != "+") + sb.AppendLine($"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"] + ")",-40} | **{mods,-10}** | /b/{item["beatmap_id"]}"); + else + sb.AppendLine($"{pp + "pp",-7} | {acc + "%",-7} | {map["artist"] + "-" + map["title"] + " (" + map["version"] + ")",-40} | /b/{item["beatmap_id"]}"); + } + sb.Append("```"); + await channel.SendMessageAsync(sb.ToString()).ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync("Something went wrong."); + _log.Warn(ex, "Osu5 command failed"); + } + + } + } + + //https://osu.ppy.sh/wiki/Accuracy + private static Double CalculateAcc(JToken play, int mode) + { + if (mode == 0) + { + var hitPoints = Double.Parse($"{play["count50"]}") * 50 + Double.Parse($"{play["count100"]}") * 100 + Double.Parse($"{play["count300"]}") * 300; + var totalHits = Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countmiss"]}"); + totalHits *= 300; + return Math.Round(hitPoints / totalHits * 100, 2); + } + else if (mode == 1) + { + var hitPoints = Double.Parse($"{play["countmiss"]}") * 0 + Double.Parse($"{play["count100"]}") * 0.5 + Double.Parse($"{play["count300"]}") * 1; + var totalHits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}"); + hitPoints *= 300; + totalHits *= 300; + return Math.Round(hitPoints / totalHits * 100, 2); + } + else if (mode == 2) + { + var fruitsCaught = Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}"); + var totalFruits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countkatu"]}"); + return Math.Round(fruitsCaught / totalFruits * 100, 2); + } + else + { + var hitPoints = Double.Parse($"{play["count50"]}") * 50 + Double.Parse($"{play["count100"]}") * 100 + Double.Parse($"{play["countkatu"]}") * 200 + (Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countgeki"]}")) * 300; + var totalHits = Double.Parse($"{play["countmiss"]}") + Double.Parse($"{play["count50"]}") + Double.Parse($"{play["count100"]}") + Double.Parse($"{play["countkatu"]}") + Double.Parse($"{play["count300"]}") + Double.Parse($"{play["countgeki"]}"); + totalHits *= 300; + return Math.Round(hitPoints / totalHits * 100, 2); + } + } + + private static string ResolveMap(string mapLink) + { + Match s = new Regex(@"osu.ppy.sh\/s\/", RegexOptions.IgnoreCase).Match(mapLink); + Match b = new Regex(@"osu.ppy.sh\/b\/", RegexOptions.IgnoreCase).Match(mapLink); + Match p = new Regex(@"osu.ppy.sh\/p\/", RegexOptions.IgnoreCase).Match(mapLink); + Match m = new Regex(@"&m=", RegexOptions.IgnoreCase).Match(mapLink); + if (s.Success) + { + var mapId = mapLink.Substring(mapLink.IndexOf("/s/") + 3); + return $"s={mapId}"; + } + else if (b.Success) + { + if (m.Success) + return $"b={mapLink.Substring(mapLink.IndexOf("/b/") + 3, mapLink.IndexOf("&m") - (mapLink.IndexOf("/b/") + 3))}"; + else + return $"b={mapLink.Substring(mapLink.IndexOf("/b/") + 3)}"; + } + else if (p.Success) + { + if (m.Success) + return $"b={mapLink.Substring(mapLink.IndexOf("?b=") + 3, mapLink.IndexOf("&m") - (mapLink.IndexOf("?b=") + 3))}"; + else + return $"b={mapLink.Substring(mapLink.IndexOf("?b=") + 3)}"; + } + else + { + return $"s={mapLink}"; //just a default incase an ID number was provided by itself (non-url)? + } + } + + private static int ResolveGameMode(string mode) + { + switch (mode.ToLower()) + { + case "std": + case "standard": + return 0; + case "taiko": + return 1; + case "ctb": + case "catchthebeat": + return 2; + case "mania": + case "osu!mania": + return 3; + default: + return 0; + } + } + + //https://github.com/ppy/osu-api/wiki#mods + private static string ResolveMods(int mods) + { + var modString = $"+"; + + if (IsBitSet(mods, 0)) + modString += "NF"; + if (IsBitSet(mods, 1)) + modString += "EZ"; + if (IsBitSet(mods, 8)) + modString += "HT"; + + if (IsBitSet(mods, 3)) + modString += "HD"; + if (IsBitSet(mods, 4)) + modString += "HR"; + if (IsBitSet(mods, 6) && !IsBitSet(mods, 9)) + modString += "DT"; + if (IsBitSet(mods, 9)) + modString += "NC"; + if (IsBitSet(mods, 10)) + modString += "FL"; + + if (IsBitSet(mods, 5)) + modString += "SD"; + if (IsBitSet(mods, 14)) + modString += "PF"; + + if (IsBitSet(mods, 7)) + modString += "RX"; + if (IsBitSet(mods, 11)) + modString += "AT"; + if (IsBitSet(mods, 12)) + modString += "SO"; + return modString; + } + + private static bool IsBitSet(int mods, int pos) => + (mods & (1 << pos)) != 0; + } + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/PlaceCommands.cs b/src/NadekoBot/Modules/Searches/Commands/PlaceCommands.cs new file mode 100644 index 00000000..11ebee81 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/PlaceCommands.cs @@ -0,0 +1,90 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using System; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class PlaceCommands + { + string typesStr { get; } = ""; + public PlaceCommands() + { + typesStr = $"`List of \"{NadekoBot.ModulePrefixes[typeof(Searches).Name]}place\" tags:`\n" + String.Join(", ", Enum.GetNames(typeof(PlaceType))); + } + + public enum PlaceType + { + Cage, //http://www.placecage.com + Steven, //http://www.stevensegallery.com + Beard, //http://placebeard.it + Fill, //http://www.fillmurray.com + Bear, //https://www.placebear.com + Kitten, //http://placekitten.com + Bacon, //http://baconmockup.com + Xoart, //http://xoart.link + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Placelist(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + await channel.SendMessageAsync(typesStr) + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Place(IUserMessage imsg, PlaceType placeType, uint width = 0, uint height = 0) + { + var channel = (ITextChannel)imsg.Channel; + + string url = ""; + switch (placeType) + { + case PlaceType.Cage: + url = "http://www.placecage.com"; + break; + case PlaceType.Steven: + url = "http://www.stevensegallery.com"; + break; + case PlaceType.Beard: + url = "http://placebeard.it"; + break; + case PlaceType.Fill: + url = "http://www.fillmurray.com"; + break; + case PlaceType.Bear: + url = "https://www.placebear.com"; + break; + case PlaceType.Kitten: + url = "http://placekitten.com"; + break; + case PlaceType.Bacon: + url = "http://baconmockup.com"; + break; + case PlaceType.Xoart: + url = "http://xoart.link"; + break; + } + var rng = new NadekoRandom(); + if (width <= 0 || width > 1000) + width = (uint)rng.Next(250, 850); + + if (height <= 0 || height > 1000) + height = (uint)rng.Next(250, 850); + + url += $"/{width}/{height}"; + + await channel.SendMessageAsync(url).ConfigureAwait(false); + } + } + } +} diff --git a/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs b/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs new file mode 100644 index 00000000..efb7ee93 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/PokemonSearchCommands.cs @@ -0,0 +1,83 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Modules.Searches.Models; +using Newtonsoft.Json; +using NLog; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class PokemonSearchCommands + { + private static Dictionary pokemons = new Dictionary(); + private static Dictionary pokemonAbilities = new Dictionary(); + + public const string PokemonAbilitiesFile = "data/pokemon/pokemon_abilities.json"; + + public const string PokemonListFile = "data/pokemon/pokemon_list.json"; + private Logger _log; + + public PokemonSearchCommands() + { + _log = LogManager.GetCurrentClassLogger(); + if (File.Exists(PokemonListFile)) + { + pokemons = JsonConvert.DeserializeObject>(File.ReadAllText(PokemonListFile)); + } + else + _log.Warn(PokemonListFile + " is missing. Pokemon abilities not loaded."); + if (File.Exists(PokemonAbilitiesFile)) + pokemonAbilities = JsonConvert.DeserializeObject>(File.ReadAllText(PokemonAbilitiesFile)); + else + _log.Warn(PokemonAbilitiesFile + " is missing. Pokemon abilities not loaded."); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Pokemon(IUserMessage umsg, [Remainder] string pokemon = null) + { + var channel = (ITextChannel)umsg.Channel; + + pokemon = pokemon?.Trim().ToUpperInvariant(); + if (string.IsNullOrWhiteSpace(pokemon)) + return; + + foreach (var kvp in pokemons) + { + if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant()) + { + await channel.SendMessageAsync($"`Stats for \"{kvp.Key}\" pokemon:`\n{kvp.Value}"); + return; + } + } + await channel.SendMessageAsync("`No pokemon found.`"); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task PokemonAbility(IUserMessage umsg, [Remainder] string ability = null) + { + var channel = (ITextChannel)umsg.Channel; + + ability = ability?.Trim().ToUpperInvariant().Replace(" ", ""); + if (string.IsNullOrWhiteSpace(ability)) + return; + foreach (var kvp in pokemonAbilities) + { + if (kvp.Key.ToUpperInvariant() == ability) + { + await channel.SendMessageAsync($"`Info for \"{kvp.Key}\" ability:`\n{kvp.Value}"); + return; + } + } + await channel.SendMessageAsync("`No ability found.`"); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs b/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs new file mode 100644 index 00000000..8a768b20 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/StreamNotificationCommands.cs @@ -0,0 +1,306 @@ +using Discord.Commands; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using NadekoBot.Services; +using System.Threading; +using System.Collections.Generic; +using NadekoBot.Services.Database.Models; +using System.Net.Http; +using Discord.WebSocket; +using NadekoBot.Attributes; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + public class StreamStatus + { + public StreamStatus(string link, bool isLive, string views) + { + Link = link; + IsLive = isLive; + Views = views; + } + + public bool IsLive { get; set; } + public string Link { get; set; } + public string Views { get; set; } + } + [Group] + public class StreamNotificationCommands + { + private Timer checkTimer { get; } + private ConcurrentDictionary oldCachedStatuses = new ConcurrentDictionary(); + private ConcurrentDictionary cachedStatuses = new ConcurrentDictionary(); + private bool FirstPass { get; set; } = true; + + public StreamNotificationCommands() + { + checkTimer = new Timer(async (state) => + { + oldCachedStatuses = new ConcurrentDictionary(cachedStatuses); + cachedStatuses = new ConcurrentDictionary(); + try + { + IEnumerable streams; + using (var uow = DbHandler.UnitOfWork()) + { + streams = uow.GuildConfigs.GetAllFollowedStreams(); + } + foreach (var stream in streams) + { + StreamStatus data; + try + { + data = await GetStreamStatus(stream).ConfigureAwait(false); + if (data == null) + return; + } + catch + { + continue; + } + + StreamStatus oldData; + oldCachedStatuses.TryGetValue(data.Link, out oldData); + + if (oldData == null || data.IsLive != oldData.IsLive) + { + if (FirstPass) + continue; + var server = NadekoBot.Client.GetGuild(stream.GuildId); + var channel = server?.GetTextChannel(stream.ChannelId); + if (channel == null) + continue; + var msg = $"`{stream.Username}`'s stream is now " + + $"**{(data.IsLive ? "ONLINE" : "OFFLINE")}** with " + + $"**{data.Views}** viewers."; + if (data.IsLive) + if (stream.Type == FollowedStream.FollowedStreamType.Hitbox) + msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】"; + else if (stream.Type == FollowedStream.FollowedStreamType.Twitch) + msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】"; + else if (stream.Type == FollowedStream.FollowedStreamType.Beam) + msg += $"\n`Here is the Link:`【 http://www.beam.pro/{stream.Username}/ 】"; + try { await channel.SendMessageAsync(msg).ConfigureAwait(false); } catch { } + } + } + FirstPass = false; + } + catch { } + }, null, TimeSpan.Zero, TimeSpan.FromSeconds(60)); + } + + private async Task GetStreamStatus(FollowedStream stream, bool checkCache = true) + { + bool isLive; + string response; + JObject data; + StreamStatus result; + switch (stream.Type) + { + case FollowedStream.FollowedStreamType.Hitbox: + var hitboxUrl = $"https://api.hitbox.tv/media/status/{stream.Username}"; + if (checkCache && cachedStatuses.TryGetValue(hitboxUrl, out result)) + return result; + using (var http = new HttpClient()) + { + response = await http.GetStringAsync(hitboxUrl).ConfigureAwait(false); + } + data = JObject.Parse(response); + isLive = data["media_is_live"].ToString() == "1"; + result = new StreamStatus(hitboxUrl, isLive, data["media_views"].ToString()); + cachedStatuses.TryAdd(hitboxUrl, result); + return result; + case FollowedStream.FollowedStreamType.Twitch: + var twitchUrl = $"https://api.twitch.tv/kraken/streams/{Uri.EscapeUriString(stream.Username)}?client_id=67w6z9i09xv2uoojdm9l0wsyph4hxo6"; + if (checkCache && cachedStatuses.TryGetValue(twitchUrl, out result)) + return result; + using (var http = new HttpClient()) + { + response = await http.GetStringAsync(twitchUrl).ConfigureAwait(false); + } + data = JObject.Parse(response); + isLive = !string.IsNullOrWhiteSpace(data["stream"].ToString()); + result = new StreamStatus(twitchUrl, isLive, isLive ? data["stream"]["viewers"].ToString() : "0"); + cachedStatuses.TryAdd(twitchUrl, result); + return result; + case FollowedStream.FollowedStreamType.Beam: + var beamUrl = $"https://beam.pro/api/v1/channels/{stream.Username}"; + if (checkCache && cachedStatuses.TryGetValue(beamUrl, out result)) + return result; + using (var http = new HttpClient()) + { + response = await http.GetStringAsync(beamUrl).ConfigureAwait(false); + } + data = JObject.Parse(response); + isLive = data["online"].ToObject() == true; + result = new StreamStatus(beamUrl, isLive, data["viewersCurrent"].ToString()); + cachedStatuses.TryAdd(beamUrl, result); + return result; + default: + break; + } + return null; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Hitbox(IUserMessage msg, [Remainder] string username) => + await TrackStream((ITextChannel)msg.Channel, username, FollowedStream.FollowedStreamType.Hitbox) + .ConfigureAwait(false); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Twitch(IUserMessage msg, [Remainder] string username) => + await TrackStream((ITextChannel)msg.Channel, username, FollowedStream.FollowedStreamType.Twitch) + .ConfigureAwait(false); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task Beam(IUserMessage msg, [Remainder] string username) => + await TrackStream((ITextChannel)msg.Channel, username, FollowedStream.FollowedStreamType.Beam) + .ConfigureAwait(false); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ListStreams(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + IEnumerable streams; + using (var uow = DbHandler.UnitOfWork()) + { + streams = uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams; + } + + if (!streams.Any()) + { + await channel.SendMessageAsync("You are not following any streams on this server.").ConfigureAwait(false); + return; + } + + var text = string.Join("\n", streams.Select(snc => + { + return $"`{snc.Username}`'s stream on **{channel.Guild.GetTextChannel(snc.ChannelId)?.Name}** channel. 【`{snc.Type.ToString()}`】"; + })); + + await channel.SendMessageAsync($"You are following **{streams.Count()}** streams on this server.\n\n" + text).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.ManageMessages)] + public async Task RemoveStream(IUserMessage msg, [Remainder] string username) + { + var channel = (ITextChannel)msg.Channel; + + username = username.ToLowerInvariant().Trim(); + + FollowedStream toRemove; + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.For(channel.Guild.Id); + var streams = config.FollowedStreams; + toRemove = streams.Where(fs => fs.ChannelId == channel.Id && fs.Username.ToLowerInvariant() == username).FirstOrDefault(); + if (toRemove != null) + { + config.FollowedStreams = new HashSet(streams.Except(new[] { toRemove })); + await uow.CompleteAsync(); + } + } + if (toRemove == null) + { + await channel.SendMessageAsync(":anger: No such stream.").ConfigureAwait(false); + return; + } + await channel.SendMessageAsync($":ok: Removed `{toRemove.Username}`'s stream ({toRemove.Type}) from notifications.").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task CheckStream(IUserMessage imsg, FollowedStream.FollowedStreamType platform, [Remainder] string username) + { + var channel = (ITextChannel)imsg.Channel; + + var stream = username?.Trim(); + if (string.IsNullOrWhiteSpace(stream)) + return; + try + { + var streamStatus = (await GetStreamStatus(new FollowedStream + { + Username = stream, + Type = platform + })); + if (streamStatus.IsLive) + { + await channel.SendMessageAsync($"`Streamer {username} is online with {streamStatus.Views} viewers.`"); + } + else + { + await channel.SendMessageAsync($"`Streamer {username} is offline.`"); + } + } + catch + { + await channel.SendMessageAsync("No channel found."); + } + } + + private async Task TrackStream(ITextChannel channel, string username, FollowedStream.FollowedStreamType type) + { + username = username.ToLowerInvariant().Trim(); + var stream = new FollowedStream + { + GuildId = channel.Guild.Id, + ChannelId = channel.Id, + Username = username, + Type = type, + }; + bool exists; + using (var uow = DbHandler.UnitOfWork()) + { + exists = uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams.Where(fs => fs.ChannelId == channel.Id && fs.Username.ToLowerInvariant().Trim() == username).Any(); + } + if (exists) + { + await channel.SendMessageAsync($":anger: I am already following `{username}` ({type}) stream on this channel.").ConfigureAwait(false); + return; + } + StreamStatus data; + try + { + data = await GetStreamStatus(stream).ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync(":anger: Stream probably doesn't exist.").ConfigureAwait(false); + return; + } + var msg = $"Stream is currently **{(data.IsLive ? "ONLINE" : "OFFLINE")}** with **{data.Views}** viewers"; + if (data.IsLive) + if (type == FollowedStream.FollowedStreamType.Hitbox) + msg += $"\n`Here is the Link:`【 http://www.hitbox.tv/{stream.Username}/ 】"; + else if (type == FollowedStream.FollowedStreamType.Twitch) + msg += $"\n`Here is the Link:`【 http://www.twitch.tv/{stream.Username}/ 】"; + else if (type == FollowedStream.FollowedStreamType.Beam) + msg += $"\n`Here is the Link:`【 https://beam.pro/{stream.Username}/ 】"; + using (var uow = DbHandler.UnitOfWork()) + { + uow.GuildConfigs.For(channel.Guild.Id).FollowedStreams.Add(stream); + await uow.CompleteAsync(); + } + msg = $":ok: I will notify this channel when status changes.\n{msg}"; + await channel.SendMessageAsync(msg).ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Commands/Translator.cs b/src/NadekoBot/Modules/Searches/Commands/Translator.cs new file mode 100644 index 00000000..fe711303 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/Translator.cs @@ -0,0 +1,180 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using System; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using System.Linq; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + public struct UserChannelPair + { + public ulong UserId { get; set; } + public ulong ChannelId { get; set; } + } + + [Group] + public class TranslateCommands + { + private static ConcurrentDictionary TranslatedChannels { get; } + private static ConcurrentDictionary UserLanguages { get; } + + static TranslateCommands() + { + TranslatedChannels = new ConcurrentDictionary(); + UserLanguages = new ConcurrentDictionary(); + + NadekoBot.Client.MessageReceived += (msg) => + { + var umsg = msg as IUserMessage; + if(umsg == null) + return Task.CompletedTask; + + bool autoDelete; + if (!TranslatedChannels.TryGetValue(umsg.Channel.Id, out autoDelete)) + return Task.CompletedTask; + + var t = Task.Run(async () => + { + var key = new UserChannelPair() + { + UserId = umsg.Author.Id, + ChannelId = umsg.Channel.Id, + }; + + string langs; + if (!UserLanguages.TryGetValue(key, out langs)) + return; + + try + { + var text = await TranslateInternal(umsg, langs, umsg.Resolve(UserMentionHandling.Ignore), true) + .ConfigureAwait(false); + if (autoDelete) + try { await umsg.DeleteAsync().ConfigureAwait(false); } catch { } + await umsg.Channel.SendMessageAsync($"{umsg.Author.Mention} `:` "+text.Replace("<@ ", "<@").Replace("<@! ", "<@!")).ConfigureAwait(false); + } + catch { } + + }); + return Task.CompletedTask; + }; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Translate(IUserMessage umsg, string langs, [Remainder] string text = null) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + var translation = await TranslateInternal(umsg, langs, text); + await channel.SendMessageAsync(translation).ConfigureAwait(false); + + } + catch + { + await channel.SendMessageAsync("Bad input format, or something went wrong...").ConfigureAwait(false); + } + } + + private static async Task TranslateInternal(IUserMessage umsg, string langs, [Remainder] string text = null, bool silent = false) + { + var langarr = langs.ToLowerInvariant().Split('>'); + if (langarr.Length != 2) + throw new ArgumentException(); + string from = langarr[0]; + string to = langarr[1]; + text = text?.Trim(); + if (string.IsNullOrWhiteSpace(text)) + throw new ArgumentException(); + return await GoogleTranslator.Instance.Translate(text, from, to).ConfigureAwait(false); + } + + public enum AutoDeleteAutoTranslate + { + Del, + Nodel + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + [OwnerOnly] + public async Task AutoTranslate(IUserMessage msg, AutoDeleteAutoTranslate autoDelete = AutoDeleteAutoTranslate.Nodel) + { + var channel = (ITextChannel)msg.Channel; + + if (autoDelete == AutoDeleteAutoTranslate.Del) + { + TranslatedChannels.AddOrUpdate(channel.Id, true, (key, val) => true); + try { await channel.SendMessageAsync("`Started automatic translation of messages on this channel. User messages will be auto-deleted.`").ConfigureAwait(false); } catch { } + return; + } + + bool throwaway; + if (TranslatedChannels.TryRemove(channel.Id, out throwaway)) + { + try { await channel.SendMessageAsync("`Stopped automatic translation of messages on this channel.`").ConfigureAwait(false); } catch { } + return; + } + else if (TranslatedChannels.TryAdd(channel.Id, autoDelete == AutoDeleteAutoTranslate.Del)) + { + try { await channel.SendMessageAsync("`Started automatic translation of messages on this channel.`").ConfigureAwait(false); } catch { } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task AutoTransLang(IUserMessage msg, [Remainder] string langs = null) + { + var channel = (ITextChannel)msg.Channel; + + var ucp = new UserChannelPair + { + UserId = msg.Author.Id, + ChannelId = msg.Channel.Id, + }; + + if (string.IsNullOrWhiteSpace(langs)) + { + if (UserLanguages.TryRemove(ucp, out langs)) + await channel.SendMessageAsync($"{msg.Author.Mention}'s auto-translate language has been removed.").ConfigureAwait(false); + return; + } + + var langarr = langs.ToLowerInvariant().Split('>'); + if (langarr.Length != 2) + return; + var from = langarr[0]; + var to = langarr[1]; + + if (!GoogleTranslator.Instance.Languages.Contains(from) || !GoogleTranslator.Instance.Languages.Contains(to)) + { + try { await channel.SendMessageAsync("`Invalid source and/or target Language.`").ConfigureAwait(false); } catch { } + return; + } + + UserLanguages.AddOrUpdate(ucp, langs, (key, val) => langs); + + await channel.SendMessageAsync(":ok:").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Translangs(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + await channel.SendTableAsync(GoogleTranslator.Instance.Languages, str => $"{str,-15}", columns: 3); + } + + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs b/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs new file mode 100644 index 00000000..4e901ea6 --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Commands/XkcdCommands.cs @@ -0,0 +1,83 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Services; +using Newtonsoft.Json; +using System.Net.Http; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Searches +{ + public partial class Searches + { + [Group] + public class XkcdCommands + { + private const string xkcdUrl = "https://xkcd.com"; + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(1)] + public async Task Xkcd(IUserMessage msg, string arg = null) + { + var channel = (ITextChannel)msg.Channel; + + if (arg?.ToLowerInvariant().Trim() == "latest") + { + using (var http = new HttpClient()) + { + var res = await http.GetStringAsync($"{xkcdUrl}/info.0.json").ConfigureAwait(false); + var comic = JsonConvert.DeserializeObject(res); + var sent = await channel.SendMessageAsync($"{msg.Author.Mention} " + comic.ToString()) + .ConfigureAwait(false); + + await Task.Delay(10000).ConfigureAwait(false); + + await sent.ModifyAsync(m => m.Content = sent.Content + $"\n`Alt:` {comic.Alt}"); + } + return; + } + await Xkcd(msg, new NadekoRandom().Next(1, 1750)).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(0)] + public async Task Xkcd(IUserMessage msg, int num) + { + var channel = (ITextChannel)msg.Channel; + + if (num < 1) + return; + + using (var http = new HttpClient()) + { + var res = await http.GetStringAsync($"{xkcdUrl}/{num}/info.0.json").ConfigureAwait(false); + + var comic = JsonConvert.DeserializeObject(res); + var sent = await channel.SendMessageAsync($"{msg.Author.Mention} " + comic.ToString()) + .ConfigureAwait(false); + + await Task.Delay(10000).ConfigureAwait(false); + + await sent.ModifyAsync(m => m.Content = sent.Content + $"\n`Alt:` {comic.Alt}"); + } + } + } + + public class XkcdComic + { + public int Num { get; set; } + public string Month { get; set; } + public string Year { get; set; } + [JsonProperty("safe_title")] + public string Title { get; set; } + [JsonProperty("img")] + public string ImageLink { get; set; } + public string Alt { get; set; } + + public override string ToString() + => $"`Comic:` #{Num} `Title:` {Title} `Date:` {Month}/{Year}\n{ImageLink}"; + } + } +} diff --git a/src/NadekoBot/Modules/Searches/Searches.cs b/src/NadekoBot/Modules/Searches/Searches.cs new file mode 100644 index 00000000..3154e9aa --- /dev/null +++ b/src/NadekoBot/Modules/Searches/Searches.cs @@ -0,0 +1,811 @@ +using Discord; +using Discord.Commands; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; +using System.Text; +using System.Net.Http; +using NadekoBot.Services; +using System.Threading.Tasks; +using NadekoBot.Attributes; +using System.Text.RegularExpressions; +using System.Net; +using NadekoBot.Modules.Searches.Models; +using System.Collections.Generic; +using ImageSharp; +using NadekoBot.Extensions; +using System.IO; +using NadekoBot.Modules.Searches.Commands.OMDB; + +namespace NadekoBot.Modules.Searches +{ + [NadekoModule("Searches", "~")] + public partial class Searches : DiscordModule + { + private IGoogleApiService _google { get; } + + public Searches(ILocalization loc, CommandService cmds, ShardedDiscordClient client, IGoogleApiService youtube) : base(loc, cmds, client) + { + _google = youtube; + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Weather(IUserMessage umsg, string city, string country) + { + var channel = (ITextChannel)umsg.Channel; + city = city.Replace(" ", ""); + country = city.Replace(" ", ""); + string response; + using (var http = new HttpClient()) + response = await http.GetStringAsync($"http://api.ninetales.us/nadekobot/weather/?city={city}&country={country}").ConfigureAwait(false); + + var obj = JObject.Parse(response)["weather"]; + + var embed = new EmbedBuilder() + .AddField(fb => fb.WithName("🌍 Location").WithValue($"{obj["target"]}").WithIsInline(true)) + .AddField(fb => fb.WithName("📏 Lat,Long").WithValue($"{obj["latitude"]}, {obj["longitude"]}").WithIsInline(true)) + .AddField(fb => fb.WithName("☁ Condition").WithValue($"{obj["condition"]}").WithIsInline(true)) + .AddField(fb => fb.WithName("😓 Humidity").WithValue($"{obj["humidity"]}%").WithIsInline(true)) + .AddField(fb => fb.WithName("💨 Wind Speed").WithValue($"{obj["windspeedk"]}km/h / {obj["windspeedm"]}mph").WithIsInline(true)) + .AddField(fb => fb.WithName("🌡 Temperature").WithValue($"{obj["centigrade"]}°C / {obj["fahrenheit"]}°F").WithIsInline(true)) + .AddField(fb => fb.WithName("🔆 Feels like").WithValue($"{obj["feelscentigrade"]}°C / {obj["feelsfahrenheit"]}°F").WithIsInline(true)) + .AddField(fb => fb.WithName("🌄 Sunrise").WithValue($"{obj["sunrise"]}").WithIsInline(true)) + .AddField(fb => fb.WithName("🌇 Sunset").WithValue($"{obj["sunset"]}").WithIsInline(true)) + .WithColor(NadekoBot.OkColor); + await channel.EmbedAsync(embed.Build()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Youtube(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + if (!(await ValidateQuery(channel, query).ConfigureAwait(false))) return; + var result = (await _google.GetVideosByKeywordsAsync(query, 1)).FirstOrDefault(); + if (string.IsNullOrWhiteSpace(result)) + { + await channel.SendMessageAsync("No results found for that query."); + return; + } + await channel.SendMessageAsync(result).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Imdb(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (!(await ValidateQuery(channel, query).ConfigureAwait(false))) return; + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + + var movie = await OmdbProvider.FindMovie(query); + if (movie == null) + { + await channel.SendMessageAsync("Failed to find that movie.").ConfigureAwait(false); + return; + } + await channel.SendMessageAsync(movie.ToString()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task RandomCat(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + using (var http = new HttpClient()) + { + await channel.SendMessageAsync(JObject.Parse( + await http.GetStringAsync("http://www.random.cat/meow").ConfigureAwait(false))["file"].ToString()) + .ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task RandomDog(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + using (var http = new HttpClient()) + { + await channel.SendMessageAsync("http://random.dog/" + await http.GetStringAsync("http://random.dog/woof").ConfigureAwait(false)).ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task I(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(query)) + return; + try + { + using (var http = new HttpClient()) + { + var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(query)}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&fields=items%2Flink&key={NadekoBot.Credentials.GoogleApiKey}"; + var obj = JObject.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false)); + await channel.SendMessageAsync(obj["items"][0]["link"].ToString()).ConfigureAwait(false); + } + } + catch (HttpRequestException exception) + { + if (exception.Message.Contains("403 (Forbidden)")) + { + await channel.SendMessageAsync("Daily limit reached!"); + } + else + { + await channel.SendMessageAsync("Something went wrong."); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Ir(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(query)) + return; + try + { + using (var http = new HttpClient()) + { + var rng = new NadekoRandom(); + var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(query)}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={NadekoBot.Credentials.GoogleApiKey}"; + var obj = JObject.Parse(await http.GetStringAsync(reqString).ConfigureAwait(false)); + var items = obj["items"] as JArray; + await channel.SendMessageAsync(items[0]["link"].ToString()).ConfigureAwait(false); + } + } + catch (HttpRequestException exception) + { + if (exception.Message.Contains("403 (Forbidden)")) + { + await channel.SendMessageAsync("Daily limit reached!"); + } + else + { + await channel.SendMessageAsync("Something went wrong."); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Lmgtfy(IUserMessage umsg, [Remainder] string ffs = null) + { + var channel = (ITextChannel)umsg.Channel; + + + if (string.IsNullOrWhiteSpace(ffs)) + return; + + await channel.SendMessageAsync(await _google.ShortenUrl($"")) + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Shorten(IUserMessage msg, [Remainder] string arg) + { + if (string.IsNullOrWhiteSpace(arg)) + return; + + await msg.Channel.SendMessageAsync(await NadekoBot.Google.ShortenUrl(arg).ConfigureAwait(false)); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Google(IUserMessage umsg, [Remainder] string terms = null) + { + var channel = (ITextChannel)umsg.Channel; + + + terms = terms?.Trim(); + if (string.IsNullOrWhiteSpace(terms)) + return; + await channel.SendMessageAsync($"https://google.com/search?q={ WebUtility.UrlEncode(terms).Replace(' ', '+') }") + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task MagicTheGathering(IUserMessage umsg, [Remainder] string name = null) + { + var channel = (ITextChannel)umsg.Channel; + var arg = name; + if (string.IsNullOrWhiteSpace(arg)) + { + await channel.SendMessageAsync("💢 `Please enter a card name to search for.`").ConfigureAwait(false); + return; + } + + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + string response = ""; + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + response = await http.GetStringAsync($"https://api.deckbrew.com/mtg/cards?name={Uri.EscapeUriString(arg)}") + .ConfigureAwait(false); + try + { + var items = JArray.Parse(response).Shuffle().ToList(); + if (items == null) + throw new KeyNotFoundException("Cannot find a card by that name"); + var msg = $@"```css +[☕ Magic The Gathering]: {items[0]["name"].ToString()} +[Store URL]: {await _google.ShortenUrl(items[0]["store_url"].ToString())} +[Cost]: {items[0]["cost"].ToString()} +[Description]: {items[0]["text"].ToString()} +``` +{items[0]["editions"][0]["image_url"].ToString()}"; + await channel.SendMessageAsync(msg).ConfigureAwait(false); + } + catch + { + await channel.SendMessageAsync($"💢 Error could not find the card {arg}").ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Hearthstone(IUserMessage umsg, [Remainder] string name = null) + { + var channel = (ITextChannel)umsg.Channel; + var arg = name; + if (string.IsNullOrWhiteSpace(arg)) + { + await channel.SendMessageAsync("💢 `Please enter a card name to search for.`").ConfigureAwait(false); + return; + } + + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey)) + { + await channel.SendMessageAsync("💢 `Bot owner didn't specify MashapeApiKey. You can't use this functionality.`").ConfigureAwait(false); + return; + } + + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + string response = ""; + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey); + response = await http.GetStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}") + .ConfigureAwait(false); + try + { + var items = JArray.Parse(response).Shuffle().ToList(); + var images = new List(); + if (items == null) + throw new KeyNotFoundException("Cannot find a card by that name"); + foreach (var item in items.Where(item => item.HasValues && item["img"] != null).Take(4)) + { + using (var sr = await http.GetStreamAsync(item["img"].ToString())) + { + var imgStream = new MemoryStream(); + await sr.CopyToAsync(imgStream); + imgStream.Position = 0; + images.Add(new Image(imgStream)); + } + } + string msg = null; + if (items.Count > 4) + { + msg = "⚠ Found over 4 images. Showing random 4."; + } + var ms = new MemoryStream(); + images.Merge().SaveAsPng(ms); + ms.Position = 0; + await channel.SendFileAsync(ms, arg + ".png", msg).ConfigureAwait(false); + } + catch (Exception ex) + { + await channel.SendMessageAsync($"💢 Error {ex.Message}").ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task UrbanDict(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey)) + { + await channel.SendMessageAsync("💢 `Bot owner didn't specify MashapeApiKey. You can't use this functionality.`").ConfigureAwait(false); + return; + } + + var arg = query; + if (string.IsNullOrWhiteSpace(arg)) + { + await channel.SendMessageAsync("💢 `Please enter a search term.`").ConfigureAwait(false); + return; + } + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey); + var res = await http.GetStringAsync($"https://mashape-community-urban-dictionary.p.mashape.com/define?term={Uri.EscapeUriString(arg)}").ConfigureAwait(false); + try + { + var items = JObject.Parse(res); + var sb = new StringBuilder(); + sb.AppendLine($"`Term:` {items["list"][0]["word"].ToString()}"); + sb.AppendLine($"`Definition:` {items["list"][0]["definition"].ToString()}"); + sb.Append($"`Link:` <{await _google.ShortenUrl(items["list"][0]["permalink"].ToString()).ConfigureAwait(false)}>"); + await channel.SendMessageAsync(sb.ToString()); + } + catch + { + await channel.SendMessageAsync("💢 Failed finding a definition for that term.").ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Hashtag(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + + var arg = query; + if (string.IsNullOrWhiteSpace(arg)) + { + await channel.SendMessageAsync("💢 `Please enter a search term.`").ConfigureAwait(false); + return; + } + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.MashapeKey)) + { + await channel.SendMessageAsync("💢 `Bot owner didn't specify MashapeApiKey. You can't use this functionality.`").ConfigureAwait(false); + return; + } + + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + string res = ""; + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + http.DefaultRequestHeaders.Add("X-Mashape-Key", NadekoBot.Credentials.MashapeKey); + res = await http.GetStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(arg)}.json").ConfigureAwait(false); + } + + try + { + var items = JObject.Parse(res); + var str = $@"`Hashtag:` {items["defs"]["def"]["hashtag"].ToString()} +`Definition:` {items["defs"]["def"]["text"].ToString()} +`Link:` <{await _google.ShortenUrl(items["defs"]["def"]["uri"].ToString()).ConfigureAwait(false)}>"; + await channel.SendMessageAsync(str); + } + catch + { + await channel.SendMessageAsync("💢 Failed finding a definition for that tag.").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Catfact(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + using (var http = new HttpClient()) + { + var response = await http.GetStringAsync("http://catfacts-api.appspot.com/api/facts").ConfigureAwait(false); + if (response == null) + return; + await channel.SendMessageAsync($"🐈 `{JObject.Parse(response)["facts"][0].ToString()}`").ConfigureAwait(false); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Revav(IUserMessage umsg, [Remainder] IUser usr = null) + { + var channel = (ITextChannel)umsg.Channel; + + if (usr == null) + usr = umsg.Author; + await channel.SendMessageAsync($"https://images.google.com/searchbyimage?image_url={usr.AvatarUrl}").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Revimg(IUserMessage umsg, [Remainder] string imageLink = null) + { + var channel = (ITextChannel)umsg.Channel; + imageLink = imageLink?.Trim() ?? ""; + + if (string.IsNullOrWhiteSpace(imageLink)) + return; + await channel.SendMessageAsync($"https://images.google.com/searchbyimage?image_url={imageLink}").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Safebooru(IUserMessage umsg, [Remainder] string tag = null) + { + var channel = (ITextChannel)umsg.Channel; + + tag = tag?.Trim() ?? ""; + var link = await GetSafebooruImageLink(tag).ConfigureAwait(false); + if (link == null) + await channel.SendMessageAsync("`No results.`"); + else + await channel.SendMessageAsync(link).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Wiki(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + + query = query?.Trim(); + if (string.IsNullOrWhiteSpace(query)) + return; + using (var http = new HttpClient()) + { + var result = await http.GetStringAsync("https://en.wikipedia.org//w/api.php?action=query&format=json&prop=info&redirects=1&formatversion=2&inprop=url&titles=" + Uri.EscapeDataString(query)); + var data = JsonConvert.DeserializeObject(result); + if (data.Query.Pages[0].Missing) + await channel.SendMessageAsync("`That page could not be found.`"); + else + await channel.SendMessageAsync(data.Query.Pages[0].FullUrl); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Color(IUserMessage umsg, [Remainder] string color = null) + { + var channel = (ITextChannel)umsg.Channel; + + color = color?.Trim().Replace("#", ""); + if (string.IsNullOrWhiteSpace((string)color)) + return; + var img = new Image(50, 50); + + var red = Convert.ToInt32(color.Substring(0, 2), 16); + var green = Convert.ToInt32(color.Substring(2, 2), 16); + var blue = Convert.ToInt32(color.Substring(4, 2), 16); + + img.BackgroundColor(new ImageSharp.Color(color)); + + await channel.SendFileAsync(img.ToStream(), $"{color}.png"); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Videocall(IUserMessage umsg, [Remainder] string arg = null) + { + var channel = (ITextChannel)umsg.Channel; + + try + { + var allUsrs = umsg.MentionedUsers.Append(umsg.Author); + var allUsrsArray = allUsrs.ToArray(); + var str = allUsrsArray.Aggregate("http://appear.in/", (current, usr) => current + Uri.EscapeUriString(usr.Username[0].ToString())); + str += new NadekoRandom().Next(); + foreach (var usr in allUsrsArray) + { + await (await (usr as IGuildUser).CreateDMChannelAsync()).SendMessageAsync(str).ConfigureAwait(false); + } + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Avatar(IUserMessage umsg, [Remainder] string mention = null) + { + var channel = (ITextChannel)umsg.Channel; + + var usr = umsg.MentionedUsers.FirstOrDefault(); + if (usr == null) + { + await channel.SendMessageAsync("Invalid user specified.").ConfigureAwait(false); + return; + } + await channel.SendMessageAsync(await _google.ShortenUrl(usr.AvatarUrl).ConfigureAwait(false)).ConfigureAwait(false); + } + + public static async Task GetSafebooruImageLink(string tag) + { + var rng = new NadekoRandom(); + var url = + $"http://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags={tag.Replace(" ", "_")}"; + using (var http = new HttpClient()) + { + var webpage = await http.GetStringAsync(url).ConfigureAwait(false); + var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); + if (matches.Count == 0) + return null; + var match = matches[rng.Next(0, matches.Count)]; + return "http:" + matches[rng.Next(0, matches.Count)].Groups["url"].Value; + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task BFO(IUserMessage umsg, [Remainder] string game = null) + { + var channel = (ITextChannel)umsg.Channel; + if (string.IsNullOrWhiteSpace(game)) + { + await channel.SendMessageAsync("💢 Please enter a game `(bf3, bf4)`").ConfigureAwait(false); + return; + } + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + try + { + if (game.Equals("bf3", StringComparison.OrdinalIgnoreCase)) + { + var res = await http.GetStringAsync($"http://api.bf3stats.com/global/onlinestats/").ConfigureAwait(false); + var items = JObject.Parse(res); + var sb = new StringBuilder(); + var status = items["status"]; + var x360 = items["360"]; + var ps3 = items["ps3"]; + var pc = items["pc"]; + + var response = $@"```css +[☕ BF3 Status: {status.ToString().ToUpper()}] +XBOX360: ✔[{x360.ToString()}] +PS3: ✔[{ps3.ToString()}] +PC: ✔[{pc.ToString()}] +```"; + await channel.SendMessageAsync(response); + } + else if (game.Equals("bf4", StringComparison.OrdinalIgnoreCase)) + { + var res = await http.GetStringAsync($"http://api.bf4stats.com/api/onlinePlayers?output=json").ConfigureAwait(false); + var items = JObject.Parse(res); + var sb = new StringBuilder(); + var status = !string.IsNullOrEmpty(items.ToString()) ? "OK" : "BAD"; + var pc = items["pc"]; + var ps3 = items["ps3"]; + var ps4 = items["ps4"]; + var xbox = items["xbox"]; + var xone = items["xone"]; + + sb.AppendLine("```css"); + sb.AppendLine($"[☕ BF4 Status: {status}]"); + + foreach (var i in items) { + var plat = items[i.Key]; + sb.AppendLine($"{plat["label"]}: ✔[{plat["count"]}] / ↑[{plat["peak24"]}]"); + } + + sb.Append("```"); + await channel.SendMessageAsync(sb.ToString()); + } + } catch + { + await channel.SendMessageAsync($"💢 BF3/BF4 API is most likely not working at the moment or could not find {game}.").ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task BFU(IUserMessage umsg, string platform, string game, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + if (string.IsNullOrWhiteSpace(platform) || string.IsNullOrWhiteSpace(game) || string.IsNullOrWhiteSpace(query)) + { + await channel.SendMessageAsync("💢 Please enter a platform `(pc, xbox, ps3, xone, ps4)`, game `(bf3, bf4)`, followed by a search query.").ConfigureAwait(false); + return; + } + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + try + { + if (game.Equals("bf3", StringComparison.OrdinalIgnoreCase)) + { + var res = await http.GetStringAsync($"http://api.bf3stats.com/{Uri.EscapeUriString(platform)}/playerlist/players={Uri.EscapeUriString(query)}?output=json").ConfigureAwait(false); + var items = JObject.Parse(res); + var sb = new StringBuilder(); + var playerName = items["list"][query]; + var playerTag = playerName["tag"]; + var playerCountryName = playerName["country_name"]; + var playerStats = playerName["stats"]; + var playerRank = playerStats["rank"]; + var playerRank_name = playerRank["name"]; + var playerGlobal_Kills = playerStats["global"]["kills"]; + var playerGlobal_Deaths = playerStats["global"]["deaths"]; + var playerGlobal_KD = Math.Round(Double.Parse(playerGlobal_Kills.ToString()) / Double.Parse(playerGlobal_Deaths.ToString()), 2); + var playerGlobal_Wins = playerStats["global"]["wins"]; + var playerGlobal_Losses = playerStats["global"]["losses"]; + var playerGlobal_WL = Math.Round(Double.Parse(playerGlobal_Wins.ToString()) / Double.Parse(playerGlobal_Losses.ToString()), 2); + var playerGlobal_Shots = playerStats["global"]["shots"]; + var playerGlobal_Hits = playerStats["global"]["hits"]; + var playerGlobal_Accuracy = Math.Round(Double.Parse(playerGlobal_Hits.ToString()) / Double.Parse(playerGlobal_Shots.ToString()), 2); + var playerGlobal_ELO = playerStats["global"]["elo"]; + + var response = $@"```css +[☕ BF3 Player: {query}] +Platform: [{platform.ToUpper()}] +Tag: [{playerTag.ToString()}] +K/D: [{playerGlobal_KD.ToString()}] +W/L: [{playerGlobal_WL.ToString()}] +Accuracy: %[{playerGlobal_Accuracy.ToString()}] +ELO: [{playerGlobal_ELO.ToString()}] +```"; + await channel.SendMessageAsync(response); + } else if (game.Equals("bf4", StringComparison.OrdinalIgnoreCase)) + { + var res = await http.GetStringAsync($"http://api.bf4stats.com/api/playerInfo?plat={Uri.EscapeUriString(platform)}&name={Uri.EscapeUriString(query)}&output=json").ConfigureAwait(false); + var items = JObject.Parse(res); + var sb = new StringBuilder(); + + var player = items["player"]; + var playerStats = items["stats"]; + + var playerName = player["name"]; + var playerTag = player["tag"]; + var playerPlatform = player["plat"]; + var playerKills = playerStats["kills"]; + var playerDeaths = playerStats["deaths"]; + var player_KD = Math.Round(Double.Parse(playerKills.ToString()) / Double.Parse(playerDeaths.ToString()), 2); + var playerWins = playerStats["numWins"]; + var playerRounds = playerStats["numRounds"]; + var player_WL = Math.Round(Double.Parse(playerWins.ToString()) / Double.Parse(playerRounds.ToString()), 2); + var shotsFired = playerStats["shotsFired"]; + var shotsHit = playerStats["shotsHit"]; + var accuracy = Math.Round(Double.Parse(shotsHit.ToString()) / Double.Parse(shotsFired.ToString()), 2); + var playerELO = playerStats["elo"]; + + var response = $@"```css +[☕ BF4 Player: {playerName.ToString()}] +Platform: [{playerPlatform.ToString().ToUpper()}] +Tag: [{playerTag.ToString()}] +K/D: [{player_KD.ToString()}] +W/L: [{player_WL.ToString()}] +Accuracy: %[{accuracy.ToString()}] +ELO: [{playerELO.ToString()}] +```"; + await channel.SendMessageAsync(response); + } + } + catch + { + await channel.SendMessageAsync($"💢 BF3/BF4 API is most likely not working at the moment or could not find {query}.").ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Wikia(IUserMessage umsg, string target, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + if (string.IsNullOrWhiteSpace(target) || string.IsNullOrWhiteSpace(query)) + { + await channel.SendMessageAsync("💢 Please enter a target wikia, followed by search query.").ConfigureAwait(false); + return; + } + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + try + { + var res = await http.GetStringAsync($"http://www.{Uri.EscapeUriString(target)}.wikia.com/api/v1/Search/List?query={Uri.EscapeUriString(query)}&limit=25&minArticleQuality=10&batch=1&namespaces=0%2C14").ConfigureAwait(false); + var items = JObject.Parse(res); + var found = items["items"][0]; + var response = $@"`Title:` {found["title"].ToString()} +`Quality:` {found["quality"]} +`URL:` {await NadekoBot.Google.ShortenUrl(found["url"].ToString()).ConfigureAwait(false)}"; + await channel.SendMessageAsync(response); + } + catch + { + await channel.SendMessageAsync($"💢 Failed finding `{query}`.").ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task MCPing(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + var arg = query; + if (string.IsNullOrWhiteSpace(arg)) + { + await channel.SendMessageAsync("💢 Please enter a `ip:port`.").ConfigureAwait(false); + return; + } + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + string ip = arg.Split(':')[0]; + string port = arg.Split(':')[1]; + var res = await http.GetStringAsync($"https://api.minetools.eu/ping/{Uri.EscapeUriString(ip)}/{Uri.EscapeUriString(port)}").ConfigureAwait(false); + try + { + var items = JObject.Parse(res); + var sb = new StringBuilder(); + int ping = (int)Math.Ceiling(Double.Parse(items["latency"].ToString())); + sb.AppendLine($"`Server:` {arg}"); + sb.AppendLine($"`Version:` {items["version"]["name"].ToString()} / Protocol {items["version"]["protocol"].ToString()}"); + sb.AppendLine($"`Description:` {items["description"].ToString()}"); + sb.AppendLine($"`Online Players:` {items["players"]["online"].ToString()}/{items["players"]["max"].ToString()}"); + sb.Append($"`Latency:` {ping}"); + await channel.SendMessageAsync(sb.ToString()); + } + catch + { + await channel.SendMessageAsync($"💢 Failed finding `{arg}`.").ConfigureAwait(false); + } + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task MCQ(IUserMessage umsg, [Remainder] string query = null) + { + var channel = (ITextChannel)umsg.Channel; + var arg = query; + if (string.IsNullOrWhiteSpace(arg)) + { + await channel.SendMessageAsync("💢 Please enter a `ip:port`.").ConfigureAwait(false); + return; + } + await umsg.Channel.TriggerTypingAsync().ConfigureAwait(false); + using (var http = new HttpClient()) + { + http.DefaultRequestHeaders.Clear(); + try + { + string ip = arg.Split(':')[0]; + string port = arg.Split(':')[1]; + var res = await http.GetStringAsync($"https://api.minetools.eu/query/{Uri.EscapeUriString(ip)}/{Uri.EscapeUriString(port)}").ConfigureAwait(false); + var items = JObject.Parse(res); + var sb = new StringBuilder(); + sb.AppendLine($"`Server:` {arg.ToString()} 〘Status: {items["status"]}〙"); + sb.AppendLine($"`Player List (First 5):`"); + foreach (var item in items["Playerlist"].Take(5)) + { + sb.AppendLine($"〔:rosette: {item}〕"); + } + sb.AppendLine($"`Online Players:` {items["Players"]} / {items["MaxPlayers"]}"); + sb.AppendLine($"`Plugins:` {items["Plugins"]}"); + sb.Append($"`Version:` {items["Version"]}"); + await channel.SendMessageAsync(sb.ToString()); + } + catch + { + await channel.SendMessageAsync($"💢 Failed finding server `{arg}`.").ConfigureAwait(false); + } + } + } + + public static async Task ValidateQuery(ITextChannel ch, string query) + { + if (!string.IsNullOrEmpty(query.Trim())) return true; + await ch.SendMessageAsync("Please specify search parameters.").ConfigureAwait(false); + return false; + } + } +} diff --git a/src/NadekoBot/Modules/Trello/Trello.cs b/src/NadekoBot/Modules/Trello/Trello.cs new file mode 100644 index 00000000..fbda6fab --- /dev/null +++ b/src/NadekoBot/Modules/Trello/Trello.cs @@ -0,0 +1,145 @@ +//using Discord.Modules; +//using Manatee.Trello; +//using Manatee.Trello.ManateeJson; +//using NadekoBot.Extensions; +//using NadekoBot.Modules.Permissions.Classes; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Timers; +//using Action = Manatee.Trello.Action; +////todo rewrite +//namespace NadekoBot.Modules.Trello +//{ +// public class Trello : DiscordModule +// { +// private readonly Timer t = new Timer { Interval = 2000 }; +// public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Trello; + +// public override void Install(ModuleManager manager) +// { + +// var client = manager.Client; + +// var serializer = new ManateeSerializer(); +// TrelloConfiguration.Serializer = serializer; +// TrelloConfiguration.Deserializer = serializer; +// TrelloConfiguration.JsonFactory = new ManateeFactory(); +// TrelloConfiguration.RestClientProvider = new Manatee.Trello.WebApi.WebApiClientProvider(); +// TrelloAuthorization.Default.AppKey = NadekoBot.Credentials.TrelloAppKey; +// //TrelloAuthorization.Default.UserToken = "[your user token]"; + +// Discord.Channel bound = null; +// Board board = null; + +// List last5ActionIDs = null; +// t.Elapsed += async (s, e) => +// { +// try +// { +// if (board == null || bound == null) +// return; //do nothing if there is no bound board + +// board.Refresh(); +// var cur5Actions = board.Actions.Take(board.Actions.Count() < 5 ? board.Actions.Count() : 5); +// var cur5ActionsArray = cur5Actions as Action[] ?? cur5Actions.ToArray(); + +// if (last5ActionIDs == null) +// { +// last5ActionIDs = cur5ActionsArray.Select(a => a.Id).ToList(); +// return; +// } + +// foreach (var a in cur5ActionsArray.Where(ca => !last5ActionIDs.Contains(ca.Id))) +// { +// await bound.Send("**--TRELLO NOTIFICATION--**\n" + a.ToString()).ConfigureAwait(false); +// } +// last5ActionIDs.Clear(); +// last5ActionIDs.AddRange(cur5ActionsArray.Select(a => a.Id)); +// } +// catch (Exception ex) +// { +// Console.WriteLine("Timer failed " + ex.ToString()); +// } +// }; + +// manager.CreateCommands("", cgb => +// { + +// cgb.AddCheck(PermissionChecker.Instance); + +// cgb.CreateCommand(Prefix + "bind") +// .Description("Bind a trello bot to a single channel. " + +// "You will receive notifications from your board when something is added or edited." + +// $" **Bot Owner Only!**| `{Prefix}bind [board_id]`") +// .Parameter("board_id", Discord.Commands.ParameterType.Required) +// .Do(async e => +// { +// if (!NadekoBot.IsOwner(umsg.Author.Id)) return; +// if (bound != null) return; +// try +// { +// bound = e.Channel; +// board = new Board(board_id.Trim()); +// board.Refresh(); +// await channel.SendMessageAsync("Successfully bound to this channel and board " + board.Name); +// t.Start(); +// } +// catch (Exception ex) +// { +// Console.WriteLine("Failed to join the board. " + ex.ToString()); +// } +// }); + +// cgb.CreateCommand(Prefix + "unbind") +// .Description($"Unbinds a bot from the channel and board. **Bot Owner Only!**| `{Prefix}unbind`") +// .Do(async e => +// { +// if (!NadekoBot.IsOwner(umsg.Author.Id)) return; +// if (bound == null || bound != e.Channel) return; +// t.Stop(); +// bound = null; +// board = null; +// await channel.SendMessageAsync("Successfully unbound trello from this channel.").ConfigureAwait(false); + +// }); + +// cgb.CreateCommand(Prefix + "lists") +// .Alias(Prefix + "list") +// .Description($"Lists all lists, yo ;) **Bot Owner Only!**| `{Prefix}list`") +// .Do(async e => +// { +// if (!NadekoBot.IsOwner(umsg.Author.Id)) return; +// if (bound == null || board == null || bound != e.Channel) return; +// await channel.SendMessageAsync("Lists for a board '" + board.Name + "'\n" + string.Join("\n", board.Lists.Select(l => "**• " + l.ToString() + "**"))) +// .ConfigureAwait(false); +// }); + +// cgb.CreateCommand(Prefix + "cards") +// .Description($"Lists all cards from the supplied list. You can supply either a name or an index. **Bot Owner Only!**| `{Prefix}cards index`") +// .Parameter("list_name", Discord.Commands.ParameterType.Unparsed) +// .Do(async e => +// { +// if (!NadekoBot.IsOwner(umsg.Author.Id)) return; +// if (bound == null || board == null || bound != e.Channel || list_name == null) return; + +// int num; +// var success = int.TryParse(list_name, out num); +// List list = null; +// if (success && num <= board.Lists.Count() && num > 0) +// list = board.Lists[num - 1]; +// else +// list = board.Lists.FirstOrDefault(l => l.Name == list_name); + + +// if (list != null) +// await channel.SendMessageAsync("There are " + list.Cards.Count() + " cards in a **" + list.Name + "** list\n" + string.Join("\n", list.Cards.Select(c => "**• " + c.ToString() + "**"))) +// .ConfigureAwait(false); +// else +// await channel.SendMessageAsync("No such list.") +// .ConfigureAwait(false); +// }); +// }); +// } +// } +//} diff --git a/src/NadekoBot/Modules/Utility/Commands/CalcCommand.cs b/src/NadekoBot/Modules/Utility/Commands/CalcCommand.cs new file mode 100644 index 00000000..2ec66349 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Commands/CalcCommand.cs @@ -0,0 +1,66 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using System; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Utility +{ + [Group] + public partial class Utility + { + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public static async Task Calculate(IUserMessage msg, [Remainder] string expression) + { + try + { + var expr = new NCalc.Expression(expression, NCalc.EvaluateOptions.IgnoreCase); + expr.EvaluateParameter += Expr_EvaluateParameter; + var result = expr.Evaluate(); + await msg.Reply(string.Format("⚙ `{0}`", expr.Error ?? result)); + } + catch (Exception e) + { + await msg.Reply($"Failed to evaluate: {e.Message} "); + } + } + + private static void Expr_EvaluateParameter(string name, NCalc.ParameterArgs args) + { + switch (name.ToLowerInvariant()) { + case "pi": args.Result= Math.PI; + break; + case "e": args.Result = Math.E; + break; + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task CalcOps(IUserMessage msg) + { + StringBuilder builder = new StringBuilder(); + var selection = typeof(Math).GetTypeInfo().GetMethods().Except(typeof(object).GetTypeInfo().GetMethods()).Select(x => + { + var name = x.Name; + if (x.GetParameters().Any()) + { + name += " (" + string.Join(", ", x.GetParameters().Select(y => y.IsOptional ? $"[{y.ParameterType.Name + " " + y.Name }]" : y.ParameterType.Name + " " + y.Name)) + ")"; + } + return name; + }); + foreach (var method in selection) builder.AppendLine(method); + await msg.ReplyLong(builder.ToString()); + } + } + class ExpressionContext + { + public double Pi { get; set; } = Math.PI; + } + +} diff --git a/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs new file mode 100644 index 00000000..44681852 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Commands/InfoCommands.cs @@ -0,0 +1,105 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using System; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Utility +{ + partial class Utility : DiscordModule + { + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task TogetherTube(IUserMessage imsg) + { + var channel = (ITextChannel)imsg.Channel; + + Uri target; + using (var http = new HttpClient()) + { + var res = await http.GetAsync("https://togethertube.com/room/create").ConfigureAwait(false); + target = res.RequestMessage.RequestUri; + } + + await channel.SendMessageAsync($"🎞 {imsg.Author.Mention}, **Your new video room created. Join and invite to watch videos together with friends:** {target}") + .ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ServerInfo(IUserMessage msg, string guild = null) + { + var channel = (ITextChannel)msg.Channel; + guild = guild?.ToUpperInvariant(); + IGuild server; + if (guild == null) + server = channel.Guild; + else + server = _client.GetGuilds().Where(g => g.Name.ToUpperInvariant() == guild.ToUpperInvariant()).FirstOrDefault(); + if (server == null) + return; + + var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(server.Id >> 22); + var sb = new StringBuilder(); + var users = await server.GetUsersAsync(); + sb.AppendLine($@"__`Name:`__ **{server.Name}** +__`Owner:`__ **{await server.GetUserAsync(server.OwnerId)}** +__`ID:`__ **{server.Id}** +__`Icon URL:`__ { server.IconUrl} +__`TextChannels:`__ **{(await server.GetTextChannelsAsync()).Count()}** `VoiceChannels:` **{(await server.GetVoiceChannelsAsync()).Count()}** +__`Members:`__ **{users.Count}** `-` {users.Count(u => u.Status == UserStatus.Online)}💚 {users.Count(u => u.Status == UserStatus.Idle)}🔶 {users.Count(u => u.Status == UserStatus.DoNotDisturb)}🔴 {users.Count(u=> u.Status == UserStatus.Offline || u.Status == UserStatus.Unknown)}⬛️ +__`Roles:`__ **{server.Roles.Count()}** +__`Created At:`__ **{createdAt.ToString("dd.MM.yyyy HH:mm")}** +"); + if (server.Emojis.Count() > 0) + sb.AppendLine($"__`Custom Emojis:`__ *{string.Join(", ", server.Emojis)}*"); + if (server.Features.Count() > 0) + sb.AppendLine($"__`Features:`__ **{string.Join(", ", server.Features)}**"); + if (!string.IsNullOrWhiteSpace(server.SplashUrl)) + sb.AppendLine($"__`Region:`__ **{server.VoiceRegionId}**"); + await msg.Reply(sb.ToString()).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ChannelInfo(IUserMessage msg, ITextChannel channel = null) + { + var ch = channel ?? (ITextChannel)msg.Channel; + if (ch == null) + return; + var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ch.Id >> 22); + var toReturn = $@"__`Name:`__ **#{ch.Name}** +__`ID:`__ **{ch.Id}** +__`Created At:`__ **{createdAt.ToString("dd.MM.yyyy HH:mm")}** +__`Topic:`__ {ch.Topic} +__`Users:`__ **{(await ch.GetUsersAsync()).Count()}**"; + await msg.Reply(toReturn).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task UserInfo(IUserMessage msg, IGuildUser usr = null) + { + var channel = (ITextChannel)msg.Channel; + var user = usr ?? msg.Author as IGuildUser; + if (user == null) + return; + var toReturn = $"👤 __`Name:`__ **{user.Username}#{user.Discriminator}**\n"; + if (!string.IsNullOrWhiteSpace(user.Nickname)) + toReturn += $"🆕 __`Nickname:`__ **{user.Nickname}** "; + toReturn += $@"🏷 __`ID:`__ **{user.Id}** +🎮 __`Current Game:`__ **{(user.Game?.Name == null ? "-" : user.Game.Name)}** +📅 __`Joined Server:`__ **{user.JoinedAt?.ToString("dd.MM.yyyy HH:mm")}** +🗓 __`Joined Discord:`__ **{user.CreatedAt.ToString("dd.MM.yyyy HH:mm")}** +⚔ __`Roles:`__ **({user.Roles.Count()}) - {string.Join(", ", user.Roles.Select(r => r.Name)).SanitizeMentions()}**"; + if (!string.IsNullOrWhiteSpace(user.AvatarUrl)) + toReturn += $@" +📷 __`Avatar URL:`__ **{await NadekoBot.Google.ShortenUrl(user.AvatarUrl).ConfigureAwait(false)}**"; + await msg.Reply(toReturn).ConfigureAwait(false); + } + } +} diff --git a/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs new file mode 100644 index 00000000..c1b35efe --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Commands/QuoteCommands.cs @@ -0,0 +1,145 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Utility +{ + public partial class Utility + { + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ListQuotes(IUserMessage imsg, int page = 1) + { + var channel = (ITextChannel)imsg.Channel; + + page -= 1; + + if (page < 0) + return; + + IEnumerable quotes; + using (var uow = DbHandler.UnitOfWork()) + { + quotes = uow.Quotes.GetGroup(page * 16, 16); + } + + if (quotes.Any()) + await channel.SendMessageAsync($"💬 **Page {page + 1} of quotes:**\n```xl\n" + String.Join("\n", quotes.Select((q) => $"{q.Keyword,-20} by {q.AuthorName}")) + "\n```") + .ConfigureAwait(false); + else + await channel.SendMessageAsync("ℹ️ **No quotes on this page.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ShowQuote(IUserMessage umsg, [Remainder] string keyword) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(keyword)) + return; + + keyword = keyword.ToUpperInvariant(); + + Quote quote; + using (var uow = DbHandler.Instance.GetUnitOfWork()) + { + quote = await uow.Quotes.GetRandomQuoteByKeywordAsync(channel.Guild.Id, keyword).ConfigureAwait(false); + } + + if (quote == null) + return; + + await channel.SendMessageAsync("📣 " + quote.Text.SanitizeMentions()); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task AddQuote(IUserMessage umsg, string keyword, [Remainder] string text) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(keyword) || string.IsNullOrWhiteSpace(text)) + return; + + keyword = keyword.ToUpperInvariant(); + + using (var uow = DbHandler.UnitOfWork()) + { + uow.Quotes.Add(new Quote + { + AuthorId = umsg.Author.Id, + AuthorName = umsg.Author.Username, + GuildId = channel.Guild.Id, + Keyword = keyword, + Text = text, + }); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync("✅ **Quote added.**").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task DeleteQuote(IUserMessage umsg, [Remainder] string keyword) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(keyword)) + return; + + var isAdmin = ((IGuildUser)umsg.Author).GuildPermissions.Administrator; + + keyword = keyword.ToUpperInvariant(); + string response; + using (var uow = DbHandler.UnitOfWork()) + { + var qs = uow.Quotes.GetAllQuotesByKeyword(channel.Guild.Id, keyword); + + if (qs==null || !qs.Any()) + { + response = "ℹ️ **No quotes found.**"; + return; + } + + var q = qs.Shuffle().FirstOrDefault(elem => isAdmin || elem.AuthorId == umsg.Author.Id); + + uow.Quotes.Remove(q); + await uow.CompleteAsync().ConfigureAwait(false); + response = "🗑 **Deleted a random quote.**"; + } + await channel.SendMessageAsync(response); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [RequirePermission(GuildPermission.Administrator)] + public async Task DelAllQuotes(IUserMessage umsg, [Remainder] string keyword) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(keyword)) + return; + + keyword = keyword.ToUpperInvariant(); + + using (var uow = DbHandler.UnitOfWork()) + { + var quotes = uow.Quotes.GetAllQuotesByKeyword(channel.Guild.Id, keyword); + + uow.Quotes.RemoveRange(quotes.ToArray());//wtf?! + + await uow.CompleteAsync(); + } + + await channel.SendMessageAsync($"🗑 **Deleted all quotes** with **{keyword}** keyword."); + } + } +} diff --git a/src/NadekoBot/Modules/Utility/Commands/Remind.cs b/src/NadekoBot/Modules/Utility/Commands/Remind.cs new file mode 100644 index 00000000..391e9109 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Commands/Remind.cs @@ -0,0 +1,213 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using NLog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Utility +{ + public partial class Utility + { + [Group] + public class RemindCommands + { + + Regex regex = new Regex(@"^(?:(?\d)mo)?(?:(?\d)w)?(?:(?\d{1,2})d)?(?:(?\d{1,2})h)?(?:(?\d{1,2})m)?$", + RegexOptions.Compiled | RegexOptions.Multiline); + + private string RemindMessageFormat { get; } + + IDictionary> replacements = new Dictionary> + { + { "%message%" , (r) => r.Message }, + { "%user%", (r) => $"<@!{r.UserId}>" }, + { "%target%", (r) => r.IsPrivate ? "Direct Message" : $"<#{r.ChannelId}>"} + }; + private Logger _log { get; } + + public RemindCommands() + { + _log = LogManager.GetCurrentClassLogger(); + List reminders; + using (var uow = DbHandler.UnitOfWork()) + { + reminders = uow.Reminders.GetAll().ToList(); + + RemindMessageFormat = uow.BotConfig.GetOrCreate().RemindMessageFormat; + } + + foreach (var r in reminders) + { + try { var t = StartReminder(r); } catch (Exception ex) { _log.Warn(ex); } + } + } + + private async Task StartReminder(Reminder r) + { + var now = DateTime.Now; + var twoMins = new TimeSpan(0, 2, 0); + TimeSpan time = r.When - now; + + if (time.TotalMilliseconds > int.MaxValue) + return; + + await Task.Delay(time); + try + { + IMessageChannel ch; + if (r.IsPrivate) + { + ch = await NadekoBot.Client.GetDMChannelAsync(r.ChannelId).ConfigureAwait(false); + } + else + { + ch = NadekoBot.Client.GetGuild(r.ServerId)?.GetTextChannel(r.ChannelId); + } + if (ch == null) + return; + + await ch.SendMessageAsync( + replacements.Aggregate(RemindMessageFormat, + (cur, replace) => cur.Replace(replace.Key, replace.Value(r))) + .SanitizeMentions() + ).ConfigureAwait(false); //it works trust me + } + catch (Exception ex) { _log.Warn(ex); } + finally + { + using (var uow = DbHandler.UnitOfWork()) + { + uow.Reminders.Remove(r); + await uow.CompleteAsync(); + } + } + } + + public enum MeOrHere + { + Me,Here + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(1)] + public async Task Remind(IUserMessage umsg, MeOrHere meorhere, string timeStr, [Remainder] string message) + { + var channel = (ITextChannel)umsg.Channel; + + IMessageChannel target; + if (meorhere == MeOrHere.Me) + { + target = await ((IGuildUser)umsg.Author).CreateDMChannelAsync().ConfigureAwait(false); + } + else + { + target = channel; + } + await Remind(umsg, target, timeStr, message).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [Priority(0)] + public async Task Remind(IUserMessage umsg, IMessageChannel ch, string timeStr, [Remainder] string message) + { + var channel = (ITextChannel)umsg.Channel; + + if (ch == null) + { + await channel.SendMessageAsync($"⚠️ {umsg.Author.Mention} Something went wrong (channel cannot be found) ;(").ConfigureAwait(false); + return; + } + + var m = regex.Match(timeStr); + + if (m.Length == 0) + { + await channel.SendMessageAsync("❎ **Not a valid time format.** type `-h .remind`").ConfigureAwait(false); + return; + } + + string output = ""; + var namesAndValues = new Dictionary(); + + foreach (var groupName in regex.GetGroupNames()) + { + if (groupName == "0") continue; + int value = 0; + int.TryParse(m.Groups[groupName].Value, out value); + + if (string.IsNullOrEmpty(m.Groups[groupName].Value)) + { + namesAndValues[groupName] = 0; + continue; + } + else if (value < 1 || + (groupName == "months" && value > 1) || + (groupName == "weeks" && value > 4) || + (groupName == "days" && value >= 7) || + (groupName == "hours" && value > 23) || + (groupName == "minutes" && value > 59)) + { + await channel.SendMessageAsync($"⚠️ Invalid {groupName} value.").ConfigureAwait(false); + return; + } + else + namesAndValues[groupName] = value; + output += m.Groups[groupName].Value + " " + groupName + " "; + } + var time = DateTime.Now + new TimeSpan(30 * namesAndValues["months"] + + 7 * namesAndValues["weeks"] + + namesAndValues["days"], + namesAndValues["hours"], + namesAndValues["minutes"], + 0); + + var rem = new Reminder + { + ChannelId = ch.Id, + IsPrivate = ch is IDMChannel, + When = time, + Message = message, + UserId = umsg.Author.Id, + ServerId = channel.Guild.Id + }; + + using (var uow = DbHandler.UnitOfWork()) + { + uow.Reminders.Add(rem); + await uow.CompleteAsync(); + } + + try { await channel.SendMessageAsync($"⏰ I will remind **\"{(ch is ITextChannel ? ((ITextChannel)ch).Name : umsg.Author.Username)}\"** to **\"{message.SanitizeMentions()}\"** in **{output}** `({time:d.M.yyyy.} at {time:HH:mm})`").ConfigureAwait(false); } catch { } + await StartReminder(rem); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task RemindTemplate(IUserMessage umsg, [Remainder] string arg) + { + var channel = (ITextChannel)umsg.Channel; + + if (string.IsNullOrWhiteSpace(arg)) + return; + + using (var uow = DbHandler.UnitOfWork()) + { + uow.BotConfig.GetOrCreate().RemindMessageFormat = arg.Trim(); + await uow.CompleteAsync().ConfigureAwait(false); + } + await channel.SendMessageAsync("🆗 New remind template set."); + } + } + } +} diff --git a/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs b/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs new file mode 100644 index 00000000..9c9c0aa0 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Commands/UnitConversion.cs @@ -0,0 +1,186 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using NadekoBot.Extensions; +using NadekoBot.Modules.Utility.Commands.Models; +using NadekoBot.Services; +using NadekoBot.Services.Database.Models; +using Newtonsoft.Json; +using NLog; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Modules.Utility +{ + public partial class Utility + { + [Group] + public class UnitConverterCommands + { + + public static List Units { get; set; } = new List(); + private static Logger _log; + private static Timer _timer; + private static TimeSpan updateInterval = new TimeSpan(12, 0, 0); + + static UnitConverterCommands() + { + _log = LogManager.GetCurrentClassLogger(); + + try + { + var data = JsonConvert.DeserializeObject>(File.ReadAllText("data/units.json")).Select(u => new ConvertUnit() + { + Modifier = u.Modifier, + UnitType = u.UnitType, + InternalTrigger = string.Join("|", u.Triggers) + }).ToArray(); + + using (var uow = DbHandler.UnitOfWork()) + { + if (uow.ConverterUnits.Empty()) + { + uow.ConverterUnits.AddRange(data); + uow.Complete(); + } + } + Units = data.ToList(); + } + catch (Exception e) + { + _log.Warn("Could not load units: " + e.Message); + } + } + + public UnitConverterCommands() + { + _timer = new Timer(async (obj) => await UpdateCurrency(), null, (int)updateInterval.TotalMilliseconds, (int)updateInterval.TotalMilliseconds); + + } + + public async Task UpdateCurrency() + {try + { + var currencyRates = await UpdateCurrencyRates(); + var unitTypeString = "currency"; + var range = currencyRates.ConversionRates.Select(u => new ConvertUnit() + { + InternalTrigger = u.Key, + Modifier = u.Value, + UnitType = unitTypeString + }).ToArray(); + var baseType = new ConvertUnit() + { + Triggers = new[] { currencyRates.Base }, + Modifier = decimal.One, + UnitType = unitTypeString + }; + var toRemove = Units.Where(u => u.UnitType == unitTypeString); + + using (var uow = DbHandler.UnitOfWork()) + { + uow.ConverterUnits.RemoveRange(toRemove.ToArray()); + uow.ConverterUnits.Add(baseType); + uow.ConverterUnits.AddRange(range); + + await uow.CompleteAsync().ConfigureAwait(false); + } + Units.RemoveAll(u => u.UnitType == unitTypeString); + Units.Add(baseType); + Units.AddRange(range); + _log.Info("Updated Currency"); + } + catch { + _log.Warn("Failed updating currency."); + } + } + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ConvertList(IUserMessage msg) + { + var sb = new StringBuilder("Units that can be used by the converter: \n"); + var res = Units.GroupBy(x => x.UnitType); + foreach (var group in res) + { + sb.AppendLine($"{group.Key}: ```xl"); + sb.AppendLine(string.Join(",", group.Select(x => x.Triggers.FirstOrDefault()).OrderBy(x => x))); + sb.AppendLine("```"); + } + await msg.ReplyLong(sb.ToString(), breakOn: new[] { "```xl\n", "\n" }); + } + [NadekoCommand, Usage, Description, Aliases] + public async Task Convert(IUserMessage msg, string origin, string target, decimal value) + { + var originUnit = Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(origin.ToLowerInvariant())); + var targetUnit = Units.Find(x => x.Triggers.Select(y => y.ToLowerInvariant()).Contains(target.ToLowerInvariant())); + if (originUnit == null || targetUnit == null) + { + await msg.Reply(string.Format("Cannot convert {0} to {1}: units not found", origin, target)); + return; + } + if (originUnit.UnitType != targetUnit.UnitType) + { + await msg.Reply(string.Format("Cannot convert {0} to {1}: types of unit are not equal", originUnit.Triggers.First(), targetUnit.Triggers.First())); + return; + } + decimal res; + if (originUnit.Triggers == targetUnit.Triggers) res = value; + else if (originUnit.UnitType == "temperature") + { + //don't really care too much about efficiency, so just convert to Kelvin, then to target + switch (originUnit.Triggers.First().ToUpperInvariant()) + { + case "C": + res = value + 273.15m; //celcius! + break; + case "F": + res = (value + 459.67m) * (5m / 9m); + break; + default: + res = value; + break; + } + //from Kelvin to target + switch (targetUnit.Triggers.First().ToUpperInvariant()) + { + case "C": + res = res - 273.15m; //celcius! + break; + case "F": + res = res * (9m / 5m) - 459.67m; + break; + default: + break; + } + } + else + { + if (originUnit.UnitType == "currency") + { + res = (value * targetUnit.Modifier) / originUnit.Modifier; + } + else + res = (value * originUnit.Modifier) / targetUnit.Modifier; + } + res = Math.Round(res, 4); + + await msg.Reply(string.Format("{0} {1} is equal to {2} {3}", value, (originUnit.Triggers.First() + "s").SnPl(value.IsInteger() ? (int)value : 2), res, (targetUnit.Triggers.First() + "s").SnPl(res.IsInteger() ? (int)res : 2))); + } + } + + public static async Task UpdateCurrencyRates() + { + using (var http = new HttpClient()) + { + var res = await http.GetStringAsync("http://api.fixer.io/latest").ConfigureAwait(false); + return JsonConvert.DeserializeObject(res); + } + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Modules/Utility/Models/MeasurementUnit.cs b/src/NadekoBot/Modules/Utility/Models/MeasurementUnit.cs new file mode 100644 index 00000000..017ea8b4 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Models/MeasurementUnit.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace NadekoBot.Modules.Utility.Commands.Models +{ + public class MeasurementUnit + { + public List Triggers { get; set; } + public string UnitType { get; set; } + public decimal Modifier { get; set; } + } +} diff --git a/src/NadekoBot/Modules/Utility/Models/Rates.cs b/src/NadekoBot/Modules/Utility/Models/Rates.cs new file mode 100644 index 00000000..b460a3c5 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Models/Rates.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace NadekoBot.Modules.Utility.Commands.Models +{ + public class Rates + { + public string Base { get; set; } + public DateTime Date { get; set; } + [JsonProperty("rates")] + public Dictionary ConversionRates { get; set; } + } +} diff --git a/src/NadekoBot/Modules/Utility/Utility.cs b/src/NadekoBot/Modules/Utility/Utility.cs new file mode 100644 index 00000000..4e0dddf0 --- /dev/null +++ b/src/NadekoBot/Modules/Utility/Utility.cs @@ -0,0 +1,296 @@ +using Discord; +using Discord.Commands; +using NadekoBot.Attributes; +using System; +using System.Linq; +using System.Threading.Tasks; +using NadekoBot.Services; +using System.Text; +using NadekoBot.Extensions; +using System.Text.RegularExpressions; +using System.Reflection; +using Discord.WebSocket; +using NadekoBot.Services.Impl; +using Discord.API; +using Embed = Discord.API.Embed; +using EmbedAuthor = Discord.API.EmbedAuthor; +using EmbedField = Discord.API.EmbedField; + +namespace NadekoBot.Modules.Utility +{ + + [NadekoModule("Utility", ".")] + public partial class Utility : DiscordModule + { + public Utility(ILocalization loc, CommandService cmds, ShardedDiscordClient client) : base(loc, cmds, client) + { + + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task WhosPlaying(IUserMessage umsg, [Remainder] string game = null) + { + var channel = (ITextChannel)umsg.Channel; + game = game.Trim().ToUpperInvariant(); + if (string.IsNullOrWhiteSpace(game)) + return; + var arr = (await (umsg.Channel as IGuildChannel).Guild.GetUsersAsync()) + .Where(u => u.Game?.Name?.ToUpperInvariant() == game) + .Select(u => u.Username) + .ToList(); + + int i = 0; + if (!arr.Any()) + await channel.SendErrorAsync("Nobody is playing that game.").ConfigureAwait(false); + else + await channel.SendMessageAsync("```css\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Concat(ig.Select(el => $"• {el,-35}")))) + "\n```").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task InRole(IUserMessage umsg, [Remainder] string roles) + { + if (string.IsNullOrWhiteSpace(roles)) + return; + var channel = (ITextChannel)umsg.Channel; + var arg = roles.Split(',').Select(r => r.Trim().ToUpperInvariant()); + string send = _l["ℹ️ **Here is a list of users in a specfic role:**"]; + foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str) && str != "@EVERYONE" && str != "EVERYONE")) + { + var role = channel.Guild.Roles.Where(r => r.Name.ToUpperInvariant() == roleStr).FirstOrDefault(); + if (role == null) continue; + send += $"```css\n[{role.Name}]\n"; + send += string.Join(", ", channel.Guild.GetUsers().Where(u => u.Roles.Contains(role)).Select(u => u.ToString())); + send += $"\n```"; + } + var usr = umsg.Author as IGuildUser; + while (send.Length > 2000) + { + if (!usr.GetPermissions(channel).ManageMessages) + { + await channel.SendMessageAsync($"⚠️ {usr.Mention} **you are not allowed to use this command on roles with a lot of users in them to prevent abuse.**").ConfigureAwait(false); + return; + } + var curstr = send.Substring(0, 2000); + await channel.SendMessageAsync(curstr.Substring(0, + curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1)).ConfigureAwait(false); + send = curstr.Substring(curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1) + + send.Substring(2000); + } + await channel.SendMessageAsync(send).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task CheckMyPerms(IUserMessage msg) + { + + StringBuilder builder = new StringBuilder("```http\n"); + var user = msg.Author as IGuildUser; + var perms = user.GetPermissions((ITextChannel)msg.Channel); + foreach (var p in perms.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any())) + { + builder.AppendLine($"{p.Name} : {p.GetValue(perms, null).ToString()}"); + } + + builder.Append("```"); + await msg.Reply(builder.ToString()); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task UserId(IUserMessage msg, IGuildUser target = null) + { + var usr = target ?? msg.Author; + await msg.Reply($"🆔 of the user **{ usr.Username }** is `{ usr.Id }`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task ChannelId(IUserMessage msg) + { + await msg.Reply($"ℹ️ This **Channel's ID** is `{msg.Channel.Id}`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ServerId(IUserMessage msg) + { + await msg.Reply($"ℹ️ This **Server's ID** is `{((ITextChannel)msg.Channel).Guild.Id}`").ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task Roles(IUserMessage msg, IGuildUser target, int page = 1) + { + var channel = (ITextChannel)msg.Channel; + var guild = channel.Guild; + + const int RolesPerPage = 20; + + if (page < 1 || page > 100) + return; + if (target != null) + { + await msg.Reply($"⚔ **Page #{page} of roles for {target.Username}:** ```css\n• " + string.Join("\n• ", target.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage)).SanitizeMentions() + "\n```"); + } + else + { + await msg.Reply($"⚔ **Page #{page} of all roles on this server:** ```css\n• " + string.Join("\n• ", guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage)).SanitizeMentions() + "\n```"); + } + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public Task Roles(IUserMessage msg, int page = 1) => + Roles(msg, null, page); + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + public async Task ChannelTopic(IUserMessage umsg) + { + var channel = (ITextChannel)umsg.Channel; + + var topic = channel.Topic; + if (string.IsNullOrWhiteSpace(topic)) + await channel.SendMessageAsync("❎ **No topic set.**"); + else + await channel.SendMessageAsync("ℹ️ **Topic:** " + topic); + } + + [NadekoCommand, Usage, Description, Aliases] + public async Task Stats(IUserMessage umsg) + { + var channel = umsg.Channel; + + var stats = NadekoBot.Stats; + + await channel.EmbedAsync( + new Embed() + { + Author = new EmbedAuthor() + { + Name = $"NadekoBot v{StatsService.BotVersion}", + Url = "http://nadekobot.readthedocs.io/en/latest/", + IconUrl = "https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg" + }, + Fields = new[] { + new EmbedField() { + Name = "Author", + Value = stats.Author, + Inline = true + }, + new EmbedField() { + Name = "Library", + Value = stats.Library, + Inline = true + }, + new EmbedField() { + Name = "Bot ID", + Value = NadekoBot.Client.GetCurrentUser().Id.ToString(), + Inline = true + }, + new EmbedField() { + Name = "Commands Ran", + Value = stats.CommandsRan.ToString(), + Inline = true + }, + new EmbedField() { + Name = "Messages", + Value = $"{stats.MessageCounter} [{stats.MessagesPerSecond:F2}/sec]", + Inline = true + }, + new EmbedField() { + Name = "Memory", + Value = $"{stats.Heap} MB", + Inline = true + }, + new EmbedField() { + Name = "Owner ID(s)", + Value = stats.OwnerIds, + Inline = true + }, + new EmbedField() { + Name = "Uptime", + Value = stats.GetUptimeString("\n"), + Inline = true + }, + new EmbedField() { + Name = "Presence", + Value = $"{NadekoBot.Client.GetGuilds().Count} servers\n{stats.TextChannels} Text Channels\n{stats.VoiceChannels} Voice Channels", + Inline = true + }, + + }, + Color = NadekoBot.OkColor + }); + } + + private Regex emojiFinder { get; } = new Regex(@"<:(?.+?):(?\d*)>", RegexOptions.Compiled); + [NadekoCommand, Usage, Description, Aliases] + public async Task Showemojis(IUserMessage msg, [Remainder] string emojis) + { + var matches = emojiFinder.Matches(emojis); + + var result = string.Join("\n", matches.Cast() + .Select(m => $"**Name:** {m.Groups["name"]} **Link:** http://discordapp.com/api/emojis/{m.Groups["id"]}.png")); + + await msg.Channel.SendMessageAsync(result).ConfigureAwait(false); + } + + [NadekoCommand, Usage, Description, Aliases] + [RequireContext(ContextType.Guild)] + [OwnerOnly] + public async Task ListServers(IUserMessage imsg, int page = 1) + { + var channel = (ITextChannel)imsg.Channel; + + page -= 1; + + if (page < 0) + return; + + var guilds = NadekoBot.Client.GetGuilds().OrderBy(g => g.Name).Skip((page - 1) * 15).Take(15); + + if (!guilds.Any()) + { + await channel.SendMessageAsync("❎ No servers found on that page.").ConfigureAwait(false); + return; + } + + await channel.SendMessageAsync(String.Join("\n", guilds.Select(g => $"```css\nName: {g.Name} ID:{g.Id} Members:#{g.GetUsers().Count} OwnerID: {g.OwnerId} ```"))).ConfigureAwait(false); + } + + //[NadekoCommand, Usage, Description, Aliases] + //[RequireContext(ContextType.Guild)] + //public async Task TextToImage(IUserMessage msg, [Remainder] string arg) + //{ + // var channel = (ITextChannel)msg.Channel; + + // const string bgName = "xbiy3"; + + // if (string.IsNullOrWhiteSpace(arg)) + // return; + + // using (var http = new HttpClient()) + // { + // http.AddFakeHeaders(); + + // http.DefaultRequestHeaders.Add("Host", "www.tagsmaker.com"); + // http.DefaultRequestHeaders.Add("Referer", "http://www.tagsmaker.com/"); + // http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + // http.DefaultRequestHeaders.Add("Alt-Used", "www.tagsmaker.com:443"); + + // var res = await http.GetAsync($"http://www.tagsmaker.com/tagsmaker.php?background_name=0011&tag_text={arg}&font_name=applejuiced&text_color=white&text_size=48&text_alignment=middle").ConfigureAwait(false); + + // var img = res.RequestMessage.RequestUri.Segments[1].Replace("image-", "").Replace("tag-", ""); + // var imgStream = await http.GetStreamAsync($"http://www.tagsmaker.com/upload/www.tagsmaker.com_{ img.ToString() }.png"); + // var ms = new MemoryStream(); + // await imgStream.CopyToAsync(ms).ConfigureAwait(false); + // ms.Position = 0; + // await channel.SendFileAsync(ms, arg+".png", "Provided by www.tagsmaker.com").ConfigureAwait(false); + // } + //} + } +} + diff --git a/src/NadekoBot/NadekoBot.cs b/src/NadekoBot/NadekoBot.cs new file mode 100644 index 00000000..a0d80f36 --- /dev/null +++ b/src/NadekoBot/NadekoBot.cs @@ -0,0 +1,142 @@ +using Discord; +using Discord.Commands; +using Discord.WebSocket; +using NadekoBot.Services; +using NadekoBot.Services.Impl; +using NLog; +using NLog.Config; +using NLog.Targets; +using System; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Collections.Generic; +using NadekoBot.Modules.Permissions; +using Module = Discord.Commands.Module; +using NadekoBot.TypeReaders; +using System.Collections.Concurrent; +using NadekoBot.Modules.Music; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot +{ + public class NadekoBot + { + private Logger _log; + + public static uint OkColor { get; } = 0x00ff00; + public static uint ErrorColor { get; } = 0xff0000; + + public static CommandService CommandService { get; private set; } + public static CommandHandler CommandHandler { get; private set; } + public static ShardedDiscordClient Client { get; private set; } + public static Localization Localizer { get; private set; } + public static BotCredentials Credentials { get; private set; } + + public static GoogleApiService Google { get; private set; } + public static StatsService Stats { get; private set; } + + public static ConcurrentDictionary ModulePrefixes { get; private set; } + public static bool Ready { get; private set; } + + public static IEnumerable AllGuildConfigs { get; } + + static NadekoBot() + { + SetupLogger(); + Credentials = new BotCredentials(); + + using (var uow = DbHandler.UnitOfWork()) + { + AllGuildConfigs = uow.GuildConfigs.GetAll(); + } + } + + public async Task RunAsync(params string[] args) + { + _log = LogManager.GetCurrentClassLogger(); + + _log.Info("Starting NadekoBot v" + StatsService.BotVersion); + + //create client + Client = new ShardedDiscordClient(new DiscordSocketConfig + { + AudioMode = Discord.Audio.AudioMode.Outgoing, + MessageCacheSize = 10, + LogLevel = LogSeverity.Warning, + TotalShards = Credentials.TotalShards, + ConnectionTimeout = int.MaxValue + }); + + //initialize Services + CommandService = new CommandService(); + Localizer = new Localization(); + Google = new GoogleApiService(); + CommandHandler = new CommandHandler(Client, CommandService); + Stats = new StatsService(Client, CommandHandler); + + //setup DI + var depMap = new DependencyMap(); + depMap.Add(Localizer); + depMap.Add(Client); + depMap.Add(CommandService); + depMap.Add(Google); + + + //setup typereaders + CommandService.AddTypeReader(new PermissionActionTypeReader()); + CommandService.AddTypeReader(new CommandTypeReader()); + CommandService.AddTypeReader(new ModuleTypeReader()); + CommandService.AddTypeReader(new GuildTypeReader()); + + //connect + await Client.LoginAsync(TokenType.Bot, Credentials.Token).ConfigureAwait(false); + await Client.ConnectAsync().ConfigureAwait(false); + await Client.DownloadAllUsersAsync().ConfigureAwait(false); + + _log.Info("Connected"); + + //load commands and prefixes + using (var uow = DbHandler.UnitOfWork()) + { + ModulePrefixes = new ConcurrentDictionary(uow.BotConfig.GetOrCreate().ModulePrefixes.ToDictionary(m => m.ModuleName, m => m.Prefix)); + } + // start handling messages received in commandhandler + await CommandHandler.StartHandling().ConfigureAwait(false); + + await CommandService.LoadAssembly(this.GetType().GetTypeInfo().Assembly, depMap).ConfigureAwait(false); +#if !GLOBAL_NADEKO + await CommandService.Load(new Music(Localizer, CommandService, Client, Google)).ConfigureAwait(false); +#endif + Ready = true; + Console.WriteLine(await Stats.Print().ConfigureAwait(false)); + } + + public async Task RunAndBlockAsync(params string[] args) + { + await RunAsync(args).ConfigureAwait(false); + await Task.Delay(-1).ConfigureAwait(false); + } + + private static void SetupLogger() + { + try + { + var logConfig = new LoggingConfiguration(); + var consoleTarget = new ColoredConsoleTarget(); + + consoleTarget.Layout = @"${date:format=HH\:mm\:ss} ${logger} | ${message}"; + + logConfig.AddTarget("Console", consoleTarget); + + logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget)); + + LogManager.Configuration = logConfig; + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + } + } +} diff --git a/src/NadekoBot/NadekoBot.xproj b/src/NadekoBot/NadekoBot.xproj new file mode 100644 index 00000000..902d10fa --- /dev/null +++ b/src/NadekoBot/NadekoBot.xproj @@ -0,0 +1,25 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 45ec1473-c678-4857-a544-07dfe0d0b478 + NadekoBot + .\obj + .\bin\ + v4.5.2 + + + 2.0 + + + + + + + + + \ No newline at end of file diff --git a/src/NadekoBot/Program.cs b/src/NadekoBot/Program.cs new file mode 100644 index 00000000..0c8832e2 --- /dev/null +++ b/src/NadekoBot/Program.cs @@ -0,0 +1,8 @@ +namespace NadekoBot +{ + public class Program + { + public static void Main(string[] args) => + new NadekoBot().RunAndBlockAsync(args).GetAwaiter().GetResult(); + } +} diff --git a/src/NadekoBot/Properties/AssemblyInfo.cs b/src/NadekoBot/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..ca3bd293 --- /dev/null +++ b/src/NadekoBot/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NadekoBot")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyInformationalVersion("1.0")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f8225ac4-3cbc-40b4-bcf3-1cacf276bf29")] diff --git a/src/NadekoBot/Resources/CommandStrings.Designer.cs b/src/NadekoBot/Resources/CommandStrings.Designer.cs new file mode 100644 index 00000000..d64bce83 --- /dev/null +++ b/src/NadekoBot/Resources/CommandStrings.Designer.cs @@ -0,0 +1,7838 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NadekoBot.Resources { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class CommandStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal CommandStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NadekoBot.Resources.CommandStrings", typeof(CommandStrings).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to 8ball. + /// + public static string _8ball_cmd { + get { + return ResourceManager.GetString("_8ball_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ask the 8ball a yes/no question.. + /// + public static string _8ball_desc { + get { + return ResourceManager.GetString("_8ball_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}8ball should I do something`. + /// + public static string _8ball_usage { + get { + return ResourceManager.GetString("_8ball_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to addcustreact acr. + /// + public static string addcustreact_cmd { + get { + return ResourceManager.GetString("addcustreact_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add a custom reaction with a trigger and a response. Running this command in server requires Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: <http://nadekobot.readthedocs.io/en/latest/Custom%20Reactions/>. + /// + public static string addcustreact_desc { + get { + return ResourceManager.GetString("addcustreact_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}acr "hello" Hi there %user%`. + /// + public static string addcustreact_usage { + get { + return ResourceManager.GetString("addcustreact_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to addplaying adpl. + /// + public static string addplaying_cmd { + get { + return ResourceManager.GetString("addplaying_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%. + /// + public static string addplaying_desc { + get { + return ResourceManager.GetString("addplaying_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}adpl`. + /// + public static string addplaying_usage { + get { + return ResourceManager.GetString("addplaying_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to .. + /// + public static string addquote_cmd { + get { + return ResourceManager.GetString("addquote_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adds a new quote with the specified name and message.. + /// + public static string addquote_desc { + get { + return ResourceManager.GetString("addquote_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}. sayhi Hi`. + /// + public static string addquote_usage { + get { + return ResourceManager.GetString("addquote_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to adsarm. + /// + public static string adsarm_cmd { + get { + return ResourceManager.GetString("adsarm_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles the automatic deletion of confirmations for {0}iam and {0}iamn commands.. + /// + public static string adsarm_desc { + get { + return ResourceManager.GetString("adsarm_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}adsarm`. + /// + public static string adsarm_usage { + get { + return ResourceManager.GetString("adsarm_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to allchnlmdls acm. + /// + public static string allchnlmdls_cmd { + get { + return ResourceManager.GetString("allchnlmdls_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable or disable all modules in a specified channel.. + /// + public static string allchnlmdls_desc { + get { + return ResourceManager.GetString("allchnlmdls_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}acm enable #SomeChannel`. + /// + public static string allchnlmdls_usage { + get { + return ResourceManager.GetString("allchnlmdls_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to allcmdcooldowns acmdcds. + /// + public static string allcmdcooldowns_cmd { + get { + return ResourceManager.GetString("allcmdcooldowns_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a list of all commands and their respective cooldowns.. + /// + public static string allcmdcooldowns_desc { + get { + return ResourceManager.GetString("allcmdcooldowns_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}acmdcds`. + /// + public static string allcmdcooldowns_usage { + get { + return ResourceManager.GetString("allcmdcooldowns_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to allrolemdls arm. + /// + public static string allrolemdls_cmd { + get { + return ResourceManager.GetString("allrolemdls_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable or disable all modules for a specific role.. + /// + public static string allrolemdls_desc { + get { + return ResourceManager.GetString("allrolemdls_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}arm [enable/disable] MyRole`. + /// + public static string allrolemdls_usage { + get { + return ResourceManager.GetString("allrolemdls_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to allsrvrmdls asm. + /// + public static string allsrvrmdls_cmd { + get { + return ResourceManager.GetString("allsrvrmdls_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable or disable all modules for your server.. + /// + public static string allsrvrmdls_desc { + get { + return ResourceManager.GetString("allsrvrmdls_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}asm [enable/disable]`. + /// + public static string allsrvrmdls_usage { + get { + return ResourceManager.GetString("allsrvrmdls_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to allusrmdls aum. + /// + public static string allusrmdls_cmd { + get { + return ResourceManager.GetString("allusrmdls_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable or disable all modules for a specific user.. + /// + public static string allusrmdls_desc { + get { + return ResourceManager.GetString("allusrmdls_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}aum enable @someone`. + /// + public static string allusrmdls_usage { + get { + return ResourceManager.GetString("allusrmdls_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to anime ani aq. + /// + public static string anime_cmd { + get { + return ResourceManager.GetString("anime_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queries anilist for an anime and shows the first result.. + /// + public static string anime_desc { + get { + return ResourceManager.GetString("anime_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ani aquarion evol`. + /// + public static string anime_usage { + get { + return ResourceManager.GetString("anime_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to announce. + /// + public static string announce_cmd { + get { + return ResourceManager.GetString("announce_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sends a message to all servers' general channel bot is connected to.. + /// + public static string announce_desc { + get { + return ResourceManager.GetString("announce_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}announce Useless spam`. + /// + public static string announce_usage { + get { + return ResourceManager.GetString("announce_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to antiraid. + /// + public static string antiraid_cmd { + get { + return ResourceManager.GetString("antiraid_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute). + /// + public static string antiraid_desc { + get { + return ResourceManager.GetString("antiraid_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}antiraid 5 20 Kick`. + /// + public static string antiraid_usage { + get { + return ResourceManager.GetString("antiraid_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to antispam. + /// + public static string antispam_cmd { + get { + return ResourceManager.GetString("antispam_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders.. + /// + public static string antispam_desc { + get { + return ResourceManager.GetString("antispam_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}antispam 3 Mute` or `{0}antispam 4 Kick` or `{0}antispam 6 Ban`. + /// + public static string antispam_usage { + get { + return ResourceManager.GetString("antispam_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to asar. + /// + public static string asar_cmd { + get { + return ResourceManager.GetString("asar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adds a role to the list of self-assignable roles.. + /// + public static string asar_desc { + get { + return ResourceManager.GetString("asar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}asar Gamer`. + /// + public static string asar_usage { + get { + return ResourceManager.GetString("asar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to atfbooru atf. + /// + public static string atfbooru_cmd { + get { + return ResourceManager.GetString("atfbooru_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random hentai image from atfbooru with a given tag. Tag is optional but preferred.. + /// + public static string atfbooru_desc { + get { + return ResourceManager.GetString("atfbooru_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}atfbooru yuri+kissing`. + /// + public static string atfbooru_usage { + get { + return ResourceManager.GetString("atfbooru_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to autoassignrole aar. + /// + public static string autoassignrole_cmd { + get { + return ResourceManager.GetString("autoassignrole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Automaticaly assigns a specified role to every user who joins the server.. + /// + public static string autoassignrole_desc { + get { + return ResourceManager.GetString("autoassignrole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}aar` to disable, `{0}aar Role Name` to enable. + /// + public static string autoassignrole_usage { + get { + return ResourceManager.GetString("autoassignrole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to autoplay ap. + /// + public static string autoplay_cmd { + get { + return ResourceManager.GetString("autoplay_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty). + /// + public static string autoplay_desc { + get { + return ResourceManager.GetString("autoplay_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ap`. + /// + public static string autoplay_usage { + get { + return ResourceManager.GetString("autoplay_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to autotranslang atl. + /// + public static string autotranslang_cmd { + get { + return ResourceManager.GetString("autotranslang_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}atl en>fr`. + /// + public static string autotranslang_desc { + get { + return ResourceManager.GetString("autotranslang_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets your source and target language to be used with `{0}at`. Specify no arguments to remove previously set value.. + /// + public static string autotranslang_usage { + get { + return ResourceManager.GetString("autotranslang_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to autotrans at. + /// + public static string autotranslate_cmd { + get { + return ResourceManager.GetString("autotranslate_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starts automatic translation of all messages by users who set their `{0}atl` in this channel. You can set "del" argument to automatically delete all translated user messages.. + /// + public static string autotranslate_desc { + get { + return ResourceManager.GetString("autotranslate_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}at` or `{0}at del`. + /// + public static string autotranslate_usage { + get { + return ResourceManager.GetString("autotranslate_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to avatar av. + /// + public static string avatar_cmd { + get { + return ResourceManager.GetString("avatar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a mentioned person's avatar.. + /// + public static string avatar_desc { + get { + return ResourceManager.GetString("avatar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}av "@SomeGuy"`. + /// + public static string avatar_usage { + get { + return ResourceManager.GetString("avatar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to award. + /// + public static string award_cmd { + get { + return ResourceManager.GetString("award_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role.. + /// + public static string award_desc { + get { + return ResourceManager.GetString("award_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}award 100 @person` or `{0}award 5 Role Of Gamblers`. + /// + public static string award_usage { + get { + return ResourceManager.GetString("award_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ban b. + /// + public static string ban_cmd { + get { + return ResourceManager.GetString("ban_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bans a user by ID or name with an optional message.. + /// + public static string ban_desc { + get { + return ResourceManager.GetString("ban_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}b "@some Guy" Your behaviour is toxic.`. + /// + public static string ban_usage { + get { + return ResourceManager.GetString("ban_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to beam bm. + /// + public static string beam_cmd { + get { + return ResourceManager.GetString("beam_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Notifies this channel when a certain user starts streaming.. + /// + public static string beam_desc { + get { + return ResourceManager.GetString("beam_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}beam SomeStreamer`. + /// + public static string beam_usage { + get { + return ResourceManager.GetString("beam_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to betflip bf. + /// + public static string betflip_cmd { + get { + return ResourceManager.GetString("betflip_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet.. + /// + public static string betflip_desc { + get { + return ResourceManager.GetString("betflip_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}bf 5 heads` or `{0}bf 3 t`. + /// + public static string betflip_usage { + get { + return ResourceManager.GetString("betflip_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to betroll br. + /// + public static string betroll_cmd { + get { + return ResourceManager.GetString("betroll_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x3 and 100 x10.. + /// + public static string betroll_desc { + get { + return ResourceManager.GetString("betroll_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}br 5`. + /// + public static string betroll_usage { + get { + return ResourceManager.GetString("betroll_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to bfonline bfo. + /// + public static string bfo_cmd { + get { + return ResourceManager.GetString("bfo_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gives you online players for BF3 and BF4. + /// + public static string bfo_desc { + get { + return ResourceManager.GetString("bfo_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}bfo bf3` or `{0}bfo bf4`. + /// + public static string bfo_usage { + get { + return ResourceManager.GetString("bfo_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to bfuser bfu. + /// + public static string bfu_cmd { + get { + return ResourceManager.GetString("bfu_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gives you back a battlefield user's stats.. + /// + public static string bfu_desc { + get { + return ResourceManager.GetString("bfu_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}bfu platform game user`. + /// + public static string bfu_usage { + get { + return ResourceManager.GetString("bfu_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to boobs. + /// + public static string boobs_cmd { + get { + return ResourceManager.GetString("boobs_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Real adult content.. + /// + public static string boobs_desc { + get { + return ResourceManager.GetString("boobs_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}boobs`. + /// + public static string boobs_usage { + get { + return ResourceManager.GetString("boobs_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to butts ass butt. + /// + public static string butts_cmd { + get { + return ResourceManager.GetString("butts_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Real adult content.. + /// + public static string butts_desc { + get { + return ResourceManager.GetString("butts_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}butts` or `{0}ass`. + /// + public static string butts_usage { + get { + return ResourceManager.GetString("butts_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to bye. + /// + public static string bye_cmd { + get { + return ResourceManager.GetString("bye_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles anouncements on the current channel when someone leaves the server.. + /// + public static string bye_desc { + get { + return ResourceManager.GetString("bye_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}bye`. + /// + public static string bye_usage { + get { + return ResourceManager.GetString("bye_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to byedel. + /// + public static string byedel_cmd { + get { + return ResourceManager.GetString("byedel_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set 0 to disable automatic deletion.. + /// + public static string byedel_desc { + get { + return ResourceManager.GetString("byedel_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}byedel 0` or `{0}byedel 30`. + /// + public static string byedel_usage { + get { + return ResourceManager.GetString("byedel_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to byemsg. + /// + public static string byemsg_cmd { + get { + return ResourceManager.GetString("byemsg_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message.. + /// + public static string byemsg_desc { + get { + return ResourceManager.GetString("byemsg_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}byemsg %user% has left.`. + /// + public static string byemsg_usage { + get { + return ResourceManager.GetString("byemsg_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to calcops. + /// + public static string calcops_cmd { + get { + return ResourceManager.GetString("calcops_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows all available operations in {0}calc command. + /// + public static string calcops_desc { + get { + return ResourceManager.GetString("calcops_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}calcops`. + /// + public static string calcops_usage { + get { + return ResourceManager.GetString("calcops_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to calculate calc. + /// + public static string calculate_cmd { + get { + return ResourceManager.GetString("calculate_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Evaluate a mathematical expression.. + /// + public static string calculate_desc { + get { + return ResourceManager.GetString("calculate_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}calc 1+1`. + /// + public static string calculate_usage { + get { + return ResourceManager.GetString("calculate_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cash $$. + /// + public static string cash_cmd { + get { + return ResourceManager.GetString("cash_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Check how much currency a person has. (Defaults to yourself). + /// + public static string cash_desc { + get { + return ResourceManager.GetString("cash_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}$$` or `{0}$$ @SomeGuy`. + /// + public static string cash_usage { + get { + return ResourceManager.GetString("cash_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to catfact. + /// + public static string catfact_cmd { + get { + return ResourceManager.GetString("catfact_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random catfact from <http://catfacts-api.appspot.com/api/facts>. + /// + public static string catfact_desc { + get { + return ResourceManager.GetString("catfact_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}catfact`. + /// + public static string catfact_usage { + get { + return ResourceManager.GetString("catfact_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cbl. + /// + public static string channelblacklist_cmd { + get { + return ResourceManager.GetString("channelblacklist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Either [add]s or [rem]oves a channel specified by an ID from a blacklist.. + /// + public static string channelblacklist_desc { + get { + return ResourceManager.GetString("channelblacklist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cbl rem 12312312312`. + /// + public static string channelblacklist_usage { + get { + return ResourceManager.GetString("channelblacklist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to channelid cid. + /// + public static string channelid_cmd { + get { + return ResourceManager.GetString("channelid_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows current channel ID.. + /// + public static string channelid_desc { + get { + return ResourceManager.GetString("channelid_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cid`. + /// + public static string channelid_usage { + get { + return ResourceManager.GetString("channelid_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to channelinfo cinfo. + /// + public static string channelinfo_cmd { + get { + return ResourceManager.GetString("channelinfo_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows info about the channel. If no channel is supplied, it defaults to current one.. + /// + public static string channelinfo_desc { + get { + return ResourceManager.GetString("channelinfo_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cinfo #some-channel`. + /// + public static string channelinfo_usage { + get { + return ResourceManager.GetString("channelinfo_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to channeltopic ct. + /// + public static string channeltopic_cmd { + get { + return ResourceManager.GetString("channeltopic_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sends current channel's topic as a message.. + /// + public static string channeltopic_desc { + get { + return ResourceManager.GetString("channeltopic_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ct`. + /// + public static string channeltopic_usage { + get { + return ResourceManager.GetString("channeltopic_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to chatmute. + /// + public static string chatmute_cmd { + get { + return ResourceManager.GetString("chatmute_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Prevents a mentioned user from chatting in text channels.. + /// + public static string chatmute_desc { + get { + return ResourceManager.GetString("chatmute_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}chatmute @Someone`. + /// + public static string chatmute_usage { + get { + return ResourceManager.GetString("chatmute_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to chatunmute. + /// + public static string chatunmute_cmd { + get { + return ResourceManager.GetString("chatunmute_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes a mute role previously set on a mentioned user with `{0}chatmute` which prevented him from chatting in text channels.. + /// + public static string chatunmute_desc { + get { + return ResourceManager.GetString("chatunmute_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}chatunmute @Someone`. + /// + public static string chatunmute_usage { + get { + return ResourceManager.GetString("chatunmute_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to checkmyperms. + /// + public static string checkmyperms_cmd { + get { + return ResourceManager.GetString("checkmyperms_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks your user-specific permissions on this channel.. + /// + public static string checkmyperms_desc { + get { + return ResourceManager.GetString("checkmyperms_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}checkmyperms`. + /// + public static string checkmyperms_usage { + get { + return ResourceManager.GetString("checkmyperms_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to checkstream cs. + /// + public static string checkstream_cmd { + get { + return ResourceManager.GetString("checkstream_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checks if a user is online on a certain streaming platform.. + /// + public static string checkstream_desc { + get { + return ResourceManager.GetString("checkstream_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cs twitch MyFavStreamer`. + /// + public static string checkstream_usage { + get { + return ResourceManager.GetString("checkstream_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to chnlcmd cc. + /// + public static string chnlcmd_cmd { + get { + return ResourceManager.GetString("chnlcmd_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a command's permission at the channel level.. + /// + public static string chnlcmd_desc { + get { + return ResourceManager.GetString("chnlcmd_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cc "command name" enable SomeChannel`. + /// + public static string chnlcmd_usage { + get { + return ResourceManager.GetString("chnlcmd_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to chnlfilterinv cfi. + /// + public static string chnlfilterinv_cmd { + get { + return ResourceManager.GetString("chnlfilterinv_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles automatic deleting of invites posted in the channel. Does not negate the {0}srvrfilterinv enabled setting. Does not affect Bot Owner.. + /// + public static string chnlfilterinv_desc { + get { + return ResourceManager.GetString("chnlfilterinv_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cfi`. + /// + public static string chnlfilterinv_usage { + get { + return ResourceManager.GetString("chnlfilterinv_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to chnlfilterwords cfw. + /// + public static string chnlfilterwords_cmd { + get { + return ResourceManager.GetString("chnlfilterwords_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles automatic deleting of messages containing banned words on the channel. Does not negate the {0}srvrfilterwords enabled setting. Does not affect bot owner.. + /// + public static string chnlfilterwords_desc { + get { + return ResourceManager.GetString("chnlfilterwords_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cfw`. + /// + public static string chnlfilterwords_usage { + get { + return ResourceManager.GetString("chnlfilterwords_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to chnlmdl cm. + /// + public static string chnlmdl_cmd { + get { + return ResourceManager.GetString("chnlmdl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a module's permission at the channel level.. + /// + public static string chnlmdl_desc { + get { + return ResourceManager.GetString("chnlmdl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cm "module name" enable SomeChannel`. + /// + public static string chnlmdl_usage { + get { + return ResourceManager.GetString("chnlmdl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to choose. + /// + public static string choose_cmd { + get { + return ResourceManager.GetString("choose_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Chooses a thing from a list of things. + /// + public static string choose_desc { + get { + return ResourceManager.GetString("choose_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}choose Get up;Sleep;Sleep more`. + /// + public static string choose_usage { + get { + return ResourceManager.GetString("choose_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to chucknorris cn. + /// + public static string chucknorris_cmd { + get { + return ResourceManager.GetString("chucknorris_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random>. + /// + public static string chucknorris_desc { + get { + return ResourceManager.GetString("chucknorris_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cn`. + /// + public static string chucknorris_usage { + get { + return ResourceManager.GetString("chucknorris_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to claim call c. + /// + public static string claim_cmd { + get { + return ResourceManager.GetString("claim_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 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.. + /// + public static string claim_desc { + get { + return ResourceManager.GetString("claim_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}call [war_number] [base_number] [optional_other_name]`. + /// + public static string claim_usage { + get { + return ResourceManager.GetString("claim_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to claimfinish cf. + /// + public static string claimfinish_cmd { + get { + return ResourceManager.GetString("claimfinish_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Finish your claim with 3 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else.. + /// + public static string claimfinish_desc { + get { + return ResourceManager.GetString("claimfinish_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cf 1` or `{0}cf 1 5`. + /// + public static string claimfinish_usage { + get { + return ResourceManager.GetString("claimfinish_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to claimfinish1 cf1. + /// + public static string claimfinish1_cmd { + get { + return ResourceManager.GetString("claimfinish1_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Finish your claim with 1 star if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else.. + /// + public static string claimfinish1_desc { + get { + return ResourceManager.GetString("claimfinish1_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cf1 1` or `{0}cf1 1 5`. + /// + public static string claimfinish1_usage { + get { + return ResourceManager.GetString("claimfinish1_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to claimfinish2 cf2. + /// + public static string claimfinish2_cmd { + get { + return ResourceManager.GetString("claimfinish2_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Finish your claim with 2 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else.. + /// + public static string claimfinish2_desc { + get { + return ResourceManager.GetString("claimfinish2_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cf2 1` or `{0}cf2 1 5`. + /// + public static string claimfinish2_usage { + get { + return ResourceManager.GetString("claimfinish2_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cleanup. + /// + public static string cleanup_cmd { + get { + return ResourceManager.GetString("cleanup_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cleans up hanging voice connections.. + /// + public static string cleanup_desc { + get { + return ResourceManager.GetString("cleanup_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cleanup`. + /// + public static string cleanup_usage { + get { + return ResourceManager.GetString("cleanup_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cleanvplust cv+t. + /// + public static string cleanvplust_cmd { + get { + return ResourceManager.GetString("cleanvplust_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk.. + /// + public static string cleanvplust_desc { + get { + return ResourceManager.GetString("cleanvplust_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cleanv+t`. + /// + public static string cleanvplust_usage { + get { + return ResourceManager.GetString("cleanvplust_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cleverbot. + /// + public static string cleverbot_cmd { + get { + return ResourceManager.GetString("cleverbot_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled.. + /// + public static string cleverbot_desc { + get { + return ResourceManager.GetString("cleverbot_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cleverbot`. + /// + public static string cleverbot_usage { + get { + return ResourceManager.GetString("cleverbot_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cmdcooldown cmdcd. + /// + public static string cmdcooldown_cmd { + get { + return ResourceManager.GetString("cmdcooldown_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a cooldown per user for a command. Set to 0 to remove the cooldown.. + /// + public static string cmdcooldown_desc { + get { + return ResourceManager.GetString("cmdcooldown_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cmdcd "some cmd" 5`. + /// + public static string cmdcooldown_usage { + get { + return ResourceManager.GetString("cmdcooldown_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to color clr. + /// + public static string color_cmd { + get { + return ResourceManager.GetString("color_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows you what color corresponds to that hex.. + /// + public static string color_desc { + get { + return ResourceManager.GetString("color_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}clr 00ff00`. + /// + public static string color_usage { + get { + return ResourceManager.GetString("color_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to commands cmds. + /// + public static string commands_cmd { + get { + return ResourceManager.GetString("commands_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List all of the bot's commands from a certain module. You can either specify full, or only first few letters of the module name.. + /// + public static string commands_desc { + get { + return ResourceManager.GetString("commands_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}commands Administration` or `{0}cmds Admin`. + /// + public static string commands_usage { + get { + return ResourceManager.GetString("commands_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to convert. + /// + public static string convert_cmd { + get { + return ResourceManager.GetString("convert_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Convert quantities. Use `{0}convertlist` to see supported dimensions and currencies.. + /// + public static string convert_desc { + get { + return ResourceManager.GetString("convert_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}convert m km 1000`. + /// + public static string convert_usage { + get { + return ResourceManager.GetString("convert_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to convertlist. + /// + public static string convertlist_cmd { + get { + return ResourceManager.GetString("convertlist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List of the convertible dimensions and currencies.. + /// + public static string convertlist_desc { + get { + return ResourceManager.GetString("convertlist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}convertlist`. + /// + public static string convertlist_usage { + get { + return ResourceManager.GetString("convertlist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cp. + /// + public static string cp_cmd { + get { + return ResourceManager.GetString("cp_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to We all know where this will lead you to.. + /// + public static string cp_desc { + get { + return ResourceManager.GetString("cp_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cp`. + /// + public static string cp_usage { + get { + return ResourceManager.GetString("cp_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to createrole cr. + /// + public static string createrole_cmd { + get { + return ResourceManager.GetString("createrole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a role with a given name.. + /// + public static string createrole_desc { + get { + return ResourceManager.GetString("createrole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cr Awesome Role`. + /// + public static string createrole_usage { + get { + return ResourceManager.GetString("createrole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to createwar cw. + /// + public static string createwar_cmd { + get { + return ResourceManager.GetString("createwar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name.. + /// + public static string createwar_desc { + get { + return ResourceManager.GetString("createwar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cw 15 The Enemy Clan`. + /// + public static string createwar_usage { + get { + return ResourceManager.GetString("createwar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to creatvoichanl cvch. + /// + public static string creatvoichanl_cmd { + get { + return ResourceManager.GetString("creatvoichanl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a new voice channel with a given name.. + /// + public static string creatvoichanl_desc { + get { + return ResourceManager.GetString("creatvoichanl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}cvch VoiceChannelName`. + /// + public static string creatvoichanl_usage { + get { + return ResourceManager.GetString("creatvoichanl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to creatxtchanl ctch. + /// + public static string creatxtchanl_cmd { + get { + return ResourceManager.GetString("creatxtchanl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a new text channel with a given name.. + /// + public static string creatxtchanl_desc { + get { + return ResourceManager.GetString("creatxtchanl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ctch TextChannelName`. + /// + public static string creatxtchanl_usage { + get { + return ResourceManager.GetString("creatxtchanl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to danbooru. + /// + public static string danbooru_cmd { + get { + return ResourceManager.GetString("danbooru_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +). + /// + public static string danbooru_desc { + get { + return ResourceManager.GetString("danbooru_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}danbooru yuri+kissing`. + /// + public static string danbooru_usage { + get { + return ResourceManager.GetString("danbooru_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to deafen deaf. + /// + public static string deafen_cmd { + get { + return ResourceManager.GetString("deafen_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deafens mentioned user or users.. + /// + public static string deafen_desc { + get { + return ResourceManager.GetString("deafen_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}deaf "@Someguy"` or `{0}deaf "@Someguy" "@Someguy"`. + /// + public static string deafen_usage { + get { + return ResourceManager.GetString("deafen_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to defvol dv. + /// + public static string defvol_cmd { + get { + return ResourceManager.GetString("defvol_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the default music volume when music playback is started (0-100). Persists through restarts.. + /// + public static string defvol_desc { + get { + return ResourceManager.GetString("defvol_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}dv 80`. + /// + public static string defvol_usage { + get { + return ResourceManager.GetString("defvol_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to delallq daq. + /// + public static string delallquotes_cmd { + get { + return ResourceManager.GetString("delallquotes_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes all quotes on a specified keyword.. + /// + public static string delallquotes_desc { + get { + return ResourceManager.GetString("delallquotes_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}delallq kek`. + /// + public static string delallquotes_usage { + get { + return ResourceManager.GetString("delallquotes_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to delcustreact dcr. + /// + public static string delcustreact_cmd { + get { + return ResourceManager.GetString("delcustreact_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration priviledges and removes server custom reaction.. + /// + public static string delcustreact_desc { + get { + return ResourceManager.GetString("delcustreact_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}dcr 5`. + /// + public static string delcustreact_usage { + get { + return ResourceManager.GetString("delcustreact_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to deleteplaylist delpls. + /// + public static string deleteplaylist_cmd { + get { + return ResourceManager.GetString("deleteplaylist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes a saved playlist. Only if you made it or if you are the bot owner.. + /// + public static string deleteplaylist_desc { + get { + return ResourceManager.GetString("deleteplaylist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}delpls animu-5`. + /// + public static string deleteplaylist_usage { + get { + return ResourceManager.GetString("deleteplaylist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to deletequote delq. + /// + public static string deletequote_cmd { + get { + return ResourceManager.GetString("deletequote_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes a random quote with the specified keyword. You have to either be server Administrator or the creator of the quote to delete it.. + /// + public static string deletequote_desc { + get { + return ResourceManager.GetString("deletequote_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}delq abc`. + /// + public static string deletequote_usage { + get { + return ResourceManager.GetString("deletequote_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to delmsgoncmd. + /// + public static string delmsgoncmd_cmd { + get { + return ResourceManager.GetString("delmsgoncmd_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles the automatic deletion of user's successful command message to prevent chat flood.. + /// + public static string delmsgoncmd_desc { + get { + return ResourceManager.GetString("delmsgoncmd_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}delmsgoncmd`. + /// + public static string delmsgoncmd_usage { + get { + return ResourceManager.GetString("delmsgoncmd_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to deltxtchanl dtch. + /// + public static string deltxtchanl_cmd { + get { + return ResourceManager.GetString("deltxtchanl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes a text channel with a given name.. + /// + public static string deltxtchanl_desc { + get { + return ResourceManager.GetString("deltxtchanl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}dtch TextChannelName`. + /// + public static string deltxtchanl_usage { + get { + return ResourceManager.GetString("deltxtchanl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to delvoichanl dvch. + /// + public static string delvoichanl_cmd { + get { + return ResourceManager.GetString("delvoichanl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes a voice channel with a given name.. + /// + public static string delvoichanl_desc { + get { + return ResourceManager.GetString("delvoichanl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}dvch VoiceChannelName`. + /// + public static string delvoichanl_usage { + get { + return ResourceManager.GetString("delvoichanl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to destroy d. + /// + public static string destroy_cmd { + get { + return ResourceManager.GetString("destroy_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour). + /// + public static string destroy_desc { + get { + return ResourceManager.GetString("destroy_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}d`. + /// + public static string destroy_usage { + get { + return ResourceManager.GetString("destroy_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to die. + /// + public static string die_cmd { + get { + return ResourceManager.GetString("die_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shuts the bot down.. + /// + public static string die_desc { + get { + return ResourceManager.GetString("die_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}die`. + /// + public static string die_usage { + get { + return ResourceManager.GetString("die_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to donadd. + /// + public static string donadd_cmd { + get { + return ResourceManager.GetString("donadd_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Add a donator to the database.. + /// + public static string donadd_desc { + get { + return ResourceManager.GetString("donadd_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}donadd Donate Amount`. + /// + public static string donadd_usage { + get { + return ResourceManager.GetString("donadd_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to donate. + /// + public static string donate_cmd { + get { + return ResourceManager.GetString("donate_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Instructions for helping the project financially.. + /// + public static string donate_desc { + get { + return ResourceManager.GetString("donate_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}donate`. + /// + public static string donate_usage { + get { + return ResourceManager.GetString("donate_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to donators. + /// + public static string donators_cmd { + get { + return ResourceManager.GetString("donators_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List of lovely people who donated to keep this project alive.. + /// + public static string donators_desc { + get { + return ResourceManager.GetString("donators_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}donators`. + /// + public static string donators_usage { + get { + return ResourceManager.GetString("donators_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to draw. + /// + public static string draw_cmd { + get { + return ResourceManager.GetString("draw_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck.. + /// + public static string draw_desc { + get { + return ResourceManager.GetString("draw_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}draw` or `{0}draw 5`. + /// + public static string draw_usage { + get { + return ResourceManager.GetString("draw_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to e621. + /// + public static string e621_cmd { + get { + return ResourceManager.GetString("e621_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random hentai image from e621.net with a given tag. Tag is optional but preferred. Use spaces for multiple tags.. + /// + public static string e621_desc { + get { + return ResourceManager.GetString("e621_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}e621 yuri kissing`. + /// + public static string e621_usage { + get { + return ResourceManager.GetString("e621_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to endwar ew. + /// + public static string endwar_cmd { + get { + return ResourceManager.GetString("endwar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Ends the war with a given index.. + /// + public static string endwar_desc { + get { + return ResourceManager.GetString("endwar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ew [war_number]`. + /// + public static string endwar_usage { + get { + return ResourceManager.GetString("endwar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to fw. + /// + public static string filterword_cmd { + get { + return ResourceManager.GetString("filterword_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adds or removes (if it exists) a word from the list of filtered words. Use`{0}sfw` or `{0}cfw` to toggle filtering.. + /// + public static string filterword_desc { + get { + return ResourceManager.GetString("filterword_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}fw poop`. + /// + public static string filterword_usage { + get { + return ResourceManager.GetString("filterword_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to flip. + /// + public static string flip_cmd { + get { + return ResourceManager.GetString("flip_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Flips coin(s) - heads or tails, and shows an image.. + /// + public static string flip_desc { + get { + return ResourceManager.GetString("flip_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}flip` or `{0}flip 3`. + /// + public static string flip_usage { + get { + return ResourceManager.GetString("flip_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to fwmsgs. + /// + public static string forwardmessages_cmd { + get { + return ResourceManager.GetString("forwardmessages_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles forwarding of non-command messages sent to bot's DM to the bot owners. + /// + public static string forwardmessages_desc { + get { + return ResourceManager.GetString("forwardmessages_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}fwmsgs`. + /// + public static string forwardmessages_usage { + get { + return ResourceManager.GetString("forwardmessages_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to fwtoall. + /// + public static string forwardtoall_cmd { + get { + return ResourceManager.GetString("forwardtoall_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json. + /// + public static string forwardtoall_desc { + get { + return ResourceManager.GetString("forwardtoall_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}fwtoall`. + /// + public static string forwardtoall_usage { + get { + return ResourceManager.GetString("forwardtoall_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to gelbooru. + /// + public static string gelbooru_cmd { + get { + return ResourceManager.GetString("gelbooru_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +). + /// + public static string gelbooru_desc { + get { + return ResourceManager.GetString("gelbooru_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}gelbooru yuri+kissing`. + /// + public static string gelbooru_usage { + get { + return ResourceManager.GetString("gelbooru_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to gencurrency gc. + /// + public static string gencurrency_cmd { + get { + return ResourceManager.GetString("gencurrency_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%). + /// + public static string gencurrency_desc { + get { + return ResourceManager.GetString("gencurrency_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}gc`. + /// + public static string gencurrency_usage { + get { + return ResourceManager.GetString("gencurrency_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to getlink gl. + /// + public static string getlink_cmd { + get { + return ResourceManager.GetString("getlink_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a link to the song in the queue by index, or the currently playing song by default.. + /// + public static string getlink_desc { + get { + return ResourceManager.GetString("getlink_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}gl`. + /// + public static string getlink_usage { + get { + return ResourceManager.GetString("getlink_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to give. + /// + public static string give_cmd { + get { + return ResourceManager.GetString("give_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Give someone a certain amount of currency.. + /// + public static string give_desc { + get { + return ResourceManager.GetString("give_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}give 1 "@SomeGuy"`. + /// + public static string give_usage { + get { + return ResourceManager.GetString("give_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to google g. + /// + public static string google_cmd { + get { + return ResourceManager.GetString("google_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Get a google search link for some terms.. + /// + public static string google_desc { + get { + return ResourceManager.GetString("google_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}google query`. + /// + public static string google_usage { + get { + return ResourceManager.GetString("google_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to goto. + /// + public static string goto_cmd { + get { + return ResourceManager.GetString("goto_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Goes to a specific time in seconds in a song.. + /// + public static string goto_desc { + get { + return ResourceManager.GetString("goto_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}goto 30`. + /// + public static string goto_usage { + get { + return ResourceManager.GetString("goto_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to greet. + /// + public static string greet_cmd { + get { + return ResourceManager.GetString("greet_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles anouncements on the current channel when someone joins the server.. + /// + public static string greet_desc { + get { + return ResourceManager.GetString("greet_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}greet`. + /// + public static string greet_usage { + get { + return ResourceManager.GetString("greet_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to greetdel grdel. + /// + public static string greetdel_cmd { + get { + return ResourceManager.GetString("greetdel_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set 0 to disable automatic deletion.. + /// + public static string greetdel_desc { + get { + return ResourceManager.GetString("greetdel_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}greetdel 0` or `{0}greetdel 30`. + /// + public static string greetdel_usage { + get { + return ResourceManager.GetString("greetdel_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to greetdm. + /// + public static string greetdm_cmd { + get { + return ResourceManager.GetString("greetdm_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled).. + /// + public static string greetdm_desc { + get { + return ResourceManager.GetString("greetdm_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}greetdm`. + /// + public static string greetdm_usage { + get { + return ResourceManager.GetString("greetdm_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to greetdmmsg. + /// + public static string greetdmmsg_cmd { + get { + return ResourceManager.GetString("greetdmmsg_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a new join announcement message which will be sent to the user who joined. Type %user% if you want to mention the new member. Using it with no message will show the current DM greet message.. + /// + public static string greetdmmsg_desc { + get { + return ResourceManager.GetString("greetdmmsg_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}greetdmmsg Welcome to the server, %user%`.. + /// + public static string greetdmmsg_usage { + get { + return ResourceManager.GetString("greetdmmsg_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to greetmsg. + /// + public static string greetmsg_cmd { + get { + return ResourceManager.GetString("greetmsg_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a new join announcement message which will be shown in the server's channel. Type %user% if you want to mention the new member. Using it with no message will show the current greet message.. + /// + public static string greetmsg_desc { + get { + return ResourceManager.GetString("greetmsg_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}greetmsg Welcome, %user%.`. + /// + public static string greetmsg_usage { + get { + return ResourceManager.GetString("greetmsg_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to readme guide. + /// + public static string guide_cmd { + get { + return ResourceManager.GetString("guide_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sends a readme and a guide links to the channel.. + /// + public static string guide_desc { + get { + return ResourceManager.GetString("guide_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}readme` or `{0}guide`. + /// + public static string guide_usage { + get { + return ResourceManager.GetString("guide_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to help h. + /// + public static string h_cmd { + get { + return ResourceManager.GetString("h_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Either shows a help for a single command, or DMs you help link if no arguments are specified.. + /// + public static string h_desc { + get { + return ResourceManager.GetString("h_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}h !!q` or `{0}h`. + /// + public static string h_usage { + get { + return ResourceManager.GetString("h_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to half. + /// + public static string half_cmd { + get { + return ResourceManager.GetString("half_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the music volume to 50%.. + /// + public static string half_desc { + get { + return ResourceManager.GetString("half_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}half`. + /// + public static string half_usage { + get { + return ResourceManager.GetString("half_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to #. + /// + public static string hashtag_cmd { + get { + return ResourceManager.GetString("hashtag_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searches Tagdef.com for a hashtag.. + /// + public static string hashtag_desc { + get { + return ResourceManager.GetString("hashtag_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}# ff`. + /// + public static string hashtag_usage { + get { + return ResourceManager.GetString("hashtag_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to hearthstone hs. + /// + public static string hearthstone_cmd { + get { + return ResourceManager.GetString("hearthstone_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searches for a Hearthstone card and shows its image. Takes a while to complete.. + /// + public static string hearthstone_desc { + get { + return ResourceManager.GetString("hearthstone_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}hs Ysera`. + /// + public static string hearthstone_usage { + get { + return ResourceManager.GetString("hearthstone_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to hentai. + /// + public static string hentai_cmd { + get { + return ResourceManager.GetString("hentai_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed.. + /// + public static string hentai_desc { + get { + return ResourceManager.GetString("hentai_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}hentai yuri`. + /// + public static string hentai_usage { + get { + return ResourceManager.GetString("hentai_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to hentaibomb. + /// + public static string hentaibomb_cmd { + get { + return ResourceManager.GetString("hentaibomb_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred.. + /// + public static string hentaibomb_desc { + get { + return ResourceManager.GetString("hentaibomb_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}hentaibomb yuri`. + /// + public static string hentaibomb_usage { + get { + return ResourceManager.GetString("hentaibomb_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to hgit. + /// + public static string hgit_cmd { + get { + return ResourceManager.GetString("hgit_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Generates the commandlist.md file.. + /// + public static string hgit_desc { + get { + return ResourceManager.GetString("hgit_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}hgit`. + /// + public static string hgit_usage { + get { + return ResourceManager.GetString("hgit_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to hitbox hb. + /// + public static string hitbox_cmd { + get { + return ResourceManager.GetString("hitbox_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Notifies this channel when a certain user starts streaming.. + /// + public static string hitbox_desc { + get { + return ResourceManager.GetString("hitbox_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}hitbox SomeStreamer`. + /// + public static string hitbox_usage { + get { + return ResourceManager.GetString("hitbox_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to img i. + /// + public static string i_cmd { + get { + return ResourceManager.GetString("i_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pulls the first image found using a search parameter. Use {0}ir for different results.. + /// + public static string i_desc { + get { + return ResourceManager.GetString("i_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}i cute kitten`. + /// + public static string i_usage { + get { + return ResourceManager.GetString("i_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to iam. + /// + public static string iam_cmd { + get { + return ResourceManager.GetString("iam_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adds a role to you that you choose. Role must be on a list of self-assignable roles.. + /// + public static string iam_desc { + get { + return ResourceManager.GetString("iam_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}iam Gamer`. + /// + public static string iam_usage { + get { + return ResourceManager.GetString("iam_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to iamnot iamn. + /// + public static string iamnot_cmd { + get { + return ResourceManager.GetString("iamnot_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes a role to you that you choose. Role must be on a list of self-assignable roles.. + /// + public static string iamnot_desc { + get { + return ResourceManager.GetString("iamnot_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}iamn Gamer`. + /// + public static string iamnot_usage { + get { + return ResourceManager.GetString("iamnot_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to imdb omdb. + /// + public static string imdb_cmd { + get { + return ResourceManager.GetString("imdb_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queries omdb for movies or series, show first result.. + /// + public static string imdb_desc { + get { + return ResourceManager.GetString("imdb_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}imdb Batman vs Superman`. + /// + public static string imdb_usage { + get { + return ResourceManager.GetString("imdb_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to inrole. + /// + public static string inrole_cmd { + get { + return ResourceManager.GetString("inrole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 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.. + /// + public static string inrole_desc { + get { + return ResourceManager.GetString("inrole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}inrole Role`. + /// + public static string inrole_usage { + get { + return ResourceManager.GetString("inrole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ir. + /// + public static string ir_cmd { + get { + return ResourceManager.GetString("ir_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pulls a random image using a search parameter.. + /// + public static string ir_desc { + get { + return ResourceManager.GetString("ir_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ir cute kitten`. + /// + public static string ir_usage { + get { + return ResourceManager.GetString("ir_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to jcsc. + /// + public static string jcsc_cmd { + get { + return ResourceManager.GetString("jcsc_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Joins current channel to an instance of cross server channel using the token.. + /// + public static string jcsc_desc { + get { + return ResourceManager.GetString("jcsc_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}jcsc TokenHere`. + /// + public static string jcsc_usage { + get { + return ResourceManager.GetString("jcsc_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to joinrace jr. + /// + public static string joinrace_cmd { + get { + return ResourceManager.GetString("joinrace_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win.. + /// + public static string joinrace_desc { + get { + return ResourceManager.GetString("joinrace_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}jr` or `{0}jr 5`. + /// + public static string joinrace_usage { + get { + return ResourceManager.GetString("joinrace_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to kick k. + /// + public static string kick_cmd { + get { + return ResourceManager.GetString("kick_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Kicks a mentioned user.. + /// + public static string kick_desc { + get { + return ResourceManager.GetString("kick_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}k "@some Guy" Your behaviour is toxic.`. + /// + public static string kick_usage { + get { + return ResourceManager.GetString("kick_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to konachan. + /// + public static string konachan_cmd { + get { + return ResourceManager.GetString("konachan_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random hentai image from konachan with a given tag. Tag is optional but preferred.. + /// + public static string konachan_desc { + get { + return ResourceManager.GetString("konachan_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}konachan yuri`. + /// + public static string konachan_usage { + get { + return ResourceManager.GetString("konachan_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to lcsc. + /// + public static string lcsc_cmd { + get { + return ResourceManager.GetString("lcsc_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Leaves Cross server channel instance from this channel.. + /// + public static string lcsc_desc { + get { + return ResourceManager.GetString("lcsc_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lcsc`. + /// + public static string lcsc_usage { + get { + return ResourceManager.GetString("lcsc_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to leaderboard lb. + /// + public static string leaderboard_cmd { + get { + return ResourceManager.GetString("leaderboard_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Displays bot currency leaderboard.. + /// + public static string leaderboard_desc { + get { + return ResourceManager.GetString("leaderboard_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lb`. + /// + public static string leaderboard_usage { + get { + return ResourceManager.GetString("leaderboard_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to leave. + /// + public static string leave_cmd { + get { + return ResourceManager.GetString("leave_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Makes Nadeko leave the server. Either name or id required.. + /// + public static string leave_desc { + get { + return ResourceManager.GetString("leave_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}leave 123123123331`. + /// + public static string leave_usage { + get { + return ResourceManager.GetString("leave_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to leet. + /// + public static string leet_cmd { + get { + return ResourceManager.GetString("leet_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Converts a text to leetspeak with 6 (1-6) severity levels. + /// + public static string leet_desc { + get { + return ResourceManager.GetString("leet_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}leet 3 Hello`. + /// + public static string leet_usage { + get { + return ResourceManager.GetString("leet_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to linux. + /// + public static string linux_cmd { + get { + return ResourceManager.GetString("linux_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Prints a customizable Linux interjection. + /// + public static string linux_desc { + get { + return ResourceManager.GetString("linux_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}linux Spyware Windows`. + /// + public static string linux_usage { + get { + return ResourceManager.GetString("linux_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to listcustreact lcr. + /// + public static string listcustreact_cmd { + get { + return ResourceManager.GetString("listcustreact_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists global or server custom reactions (20 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. Specifying `all` argument instead of the number will DM you a text file with a list of all custom reactions.. + /// + public static string listcustreact_desc { + get { + return ResourceManager.GetString("listcustreact_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lcr 1` or `{0}lcr all`. + /// + public static string listcustreact_usage { + get { + return ResourceManager.GetString("listcustreact_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to listcustreactg lcrg. + /// + public static string listcustreactg_cmd { + get { + return ResourceManager.GetString("listcustreactg_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions.. + /// + public static string listcustreactg_desc { + get { + return ResourceManager.GetString("listcustreactg_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lcrg 1`. + /// + public static string listcustreactg_usage { + get { + return ResourceManager.GetString("listcustreactg_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to listperms lp. + /// + public static string listperms_cmd { + get { + return ResourceManager.GetString("listperms_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions.. + /// + public static string listperms_desc { + get { + return ResourceManager.GetString("listperms_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lp` or `{0}lp 3`. + /// + public static string listperms_usage { + get { + return ResourceManager.GetString("listperms_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to listplaying lipl. + /// + public static string listplaying_cmd { + get { + return ResourceManager.GetString("listplaying_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists all playing statuses with their corresponding number.. + /// + public static string listplaying_desc { + get { + return ResourceManager.GetString("listplaying_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lipl`. + /// + public static string listplaying_usage { + get { + return ResourceManager.GetString("listplaying_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to listqueue lq. + /// + public static string listqueue_cmd { + get { + return ResourceManager.GetString("listqueue_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists 15 currently queued songs per page. Default page is 1.. + /// + public static string listqueue_desc { + get { + return ResourceManager.GetString("listqueue_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lq` or `{0}lq 2`. + /// + public static string listqueue_usage { + get { + return ResourceManager.GetString("listqueue_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to listquotes liqu. + /// + public static string listquotes_cmd { + get { + return ResourceManager.GetString("listquotes_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}liqu` or `{0}liqu 3`. + /// + public static string listquotes_desc { + get { + return ResourceManager.GetString("listquotes_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists all quotes on the server ordered alphabetically. 15 Per page.. + /// + public static string listquotes_usage { + get { + return ResourceManager.GetString("listquotes_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to listservers. + /// + public static string listservers_cmd { + get { + return ResourceManager.GetString("listservers_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists servers the bot is on with some basic info. 15 per page.. + /// + public static string listservers_desc { + get { + return ResourceManager.GetString("listservers_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}listservers 3`. + /// + public static string listservers_usage { + get { + return ResourceManager.GetString("listservers_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to liststreams ls. + /// + public static string liststreams_cmd { + get { + return ResourceManager.GetString("liststreams_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists all streams you are following on this server.. + /// + public static string liststreams_desc { + get { + return ResourceManager.GetString("liststreams_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ls`. + /// + public static string liststreams_usage { + get { + return ResourceManager.GetString("liststreams_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to listwar lw. + /// + public static string listwar_cmd { + get { + return ResourceManager.GetString("listwar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows the active war claims by a number. Shows all wars in a short way if no number is specified.. + /// + public static string listwar_desc { + get { + return ResourceManager.GetString("listwar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lw [war_number] or {0}lw`. + /// + public static string listwar_usage { + get { + return ResourceManager.GetString("listwar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to lmgtfy. + /// + public static string lmgtfy_cmd { + get { + return ResourceManager.GetString("lmgtfy_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Google something for an idiot.. + /// + public static string lmgtfy_desc { + get { + return ResourceManager.GetString("lmgtfy_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lmgtfy query`. + /// + public static string lmgtfy_usage { + get { + return ResourceManager.GetString("lmgtfy_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to load. + /// + public static string load_cmd { + get { + return ResourceManager.GetString("load_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Loads a playlist under a certain name.. + /// + public static string load_desc { + get { + return ResourceManager.GetString("load_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}load classical-1`. + /// + public static string load_usage { + get { + return ResourceManager.GetString("load_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to local lo. + /// + public static string local_cmd { + get { + return ResourceManager.GetString("local_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queues a local file by specifying a full path.. + /// + public static string local_desc { + get { + return ResourceManager.GetString("local_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lo C:/music/mysong.mp3`. + /// + public static string local_usage { + get { + return ResourceManager.GetString("local_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to localplaylst lopl. + /// + public static string localpl_cmd { + get { + return ResourceManager.GetString("localpl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queues all songs from a directory.. + /// + public static string localpl_desc { + get { + return ResourceManager.GetString("localpl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lopl C:/music/classical`. + /// + public static string localpl_usage { + get { + return ResourceManager.GetString("localpl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to logignore. + /// + public static string logignore_cmd { + get { + return ResourceManager.GetString("logignore_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel.. + /// + public static string logignore_desc { + get { + return ResourceManager.GetString("logignore_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}logignore`. + /// + public static string logignore_usage { + get { + return ResourceManager.GetString("logignore_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to logserver. + /// + public static string logserver_cmd { + get { + return ResourceManager.GetString("logserver_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logs server activity in this channel.. + /// + public static string logserver_desc { + get { + return ResourceManager.GetString("logserver_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}logserver`. + /// + public static string logserver_usage { + get { + return ResourceManager.GetString("logserver_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to lolban. + /// + public static string lolban_cmd { + get { + return ResourceManager.GetString("lolban_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows top banned champions ordered by ban rate.. + /// + public static string lolban_desc { + get { + return ResourceManager.GetString("lolban_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lolban`. + /// + public static string lolban_usage { + get { + return ResourceManager.GetString("lolban_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to lolchamp. + /// + public static string lolchamp_cmd { + get { + return ResourceManager.GetString("lolchamp_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role.. + /// + public static string lolchamp_desc { + get { + return ResourceManager.GetString("lolchamp_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lolchamp Riven` or `{0}lolchamp Annie sup`. + /// + public static string lolchamp_usage { + get { + return ResourceManager.GetString("lolchamp_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to lsar. + /// + public static string lsar_cmd { + get { + return ResourceManager.GetString("lsar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists all self-assignable roles.. + /// + public static string lsar_desc { + get { + return ResourceManager.GetString("lsar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lsar`. + /// + public static string lsar_usage { + get { + return ResourceManager.GetString("lsar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to lstfilterwords lfw. + /// + public static string lstfilterwords_cmd { + get { + return ResourceManager.GetString("lstfilterwords_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a list of filtered words.. + /// + public static string lstfilterwords_desc { + get { + return ResourceManager.GetString("lstfilterwords_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}lfw`. + /// + public static string lstfilterwords_usage { + get { + return ResourceManager.GetString("lstfilterwords_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to magicitem mi. + /// + public static string magicitem_cmd { + get { + return ResourceManager.GetString("magicitem_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items>. + /// + public static string magicitem_desc { + get { + return ResourceManager.GetString("magicitem_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}mi`. + /// + public static string magicitem_usage { + get { + return ResourceManager.GetString("magicitem_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to magicthegathering mtg. + /// + public static string magicthegathering_cmd { + get { + return ResourceManager.GetString("magicthegathering_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searches for a Magic The Gathering card.. + /// + public static string magicthegathering_desc { + get { + return ResourceManager.GetString("magicthegathering_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}magicthegathering about face` or `{0}mtg about face`. + /// + public static string magicthegathering_usage { + get { + return ResourceManager.GetString("magicthegathering_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to manga mang mq. + /// + public static string manga_cmd { + get { + return ResourceManager.GetString("manga_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queries anilist for a manga and shows the first result.. + /// + public static string manga_desc { + get { + return ResourceManager.GetString("manga_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}mq Shingeki no kyojin`. + /// + public static string manga_usage { + get { + return ResourceManager.GetString("manga_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to max. + /// + public static string max_cmd { + get { + return ResourceManager.GetString("max_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the music volume to 100%.. + /// + public static string max_desc { + get { + return ResourceManager.GetString("max_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}max`. + /// + public static string max_usage { + get { + return ResourceManager.GetString("max_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to minecraftping mcping. + /// + public static string mcping_cmd { + get { + return ResourceManager.GetString("mcping_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pings a minecraft server.. + /// + public static string mcping_desc { + get { + return ResourceManager.GetString("mcping_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}mcping 127.0.0.1:25565`. + /// + public static string mcping_usage { + get { + return ResourceManager.GetString("mcping_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to minecraftquery mcq. + /// + public static string mcq_cmd { + get { + return ResourceManager.GetString("mcq_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Finds information about a minecraft server.. + /// + public static string mcq_desc { + get { + return ResourceManager.GetString("mcq_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}mcq server:ip`. + /// + public static string mcq_usage { + get { + return ResourceManager.GetString("mcq_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to memegen. + /// + public static string memegen_cmd { + get { + return ResourceManager.GetString("memegen_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Generates a meme from memelist with top and bottom text.. + /// + public static string memegen_desc { + get { + return ResourceManager.GetString("memegen_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}memegen biw "gets iced coffee" "in the winter"`. + /// + public static string memegen_usage { + get { + return ResourceManager.GetString("memegen_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to memelist. + /// + public static string memelist_cmd { + get { + return ResourceManager.GetString("memelist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pulls a list of memes you can use with `{0}memegen` from http://memegen.link/templates/. + /// + public static string memelist_desc { + get { + return ResourceManager.GetString("memelist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}memelist`. + /// + public static string memelist_usage { + get { + return ResourceManager.GetString("memelist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to mentionrole menro. + /// + public static string mentionrole_cmd { + get { + return ResourceManager.GetString("mentionrole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission.. + /// + public static string mentionrole_desc { + get { + return ResourceManager.GetString("mentionrole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}menro RoleName`. + /// + public static string mentionrole_usage { + get { + return ResourceManager.GetString("mentionrole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to migratedata. + /// + public static string migratedata_cmd { + get { + return ResourceManager.GetString("migratedata_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Migrate data from old bot configuration. + /// + public static string migratedata_desc { + get { + return ResourceManager.GetString("migratedata_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}migratedata`. + /// + public static string migratedata_usage { + get { + return ResourceManager.GetString("migratedata_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to modules mdls. + /// + public static string modules_cmd { + get { + return ResourceManager.GetString("modules_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists all bot modules.. + /// + public static string modules_desc { + get { + return ResourceManager.GetString("modules_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}modules`. + /// + public static string modules_usage { + get { + return ResourceManager.GetString("modules_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to move mv. + /// + public static string move_cmd { + get { + return ResourceManager.GetString("move_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Moves the bot to your voice channel. (works only if music is already playing). + /// + public static string move_desc { + get { + return ResourceManager.GetString("move_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}mv`. + /// + public static string move_usage { + get { + return ResourceManager.GetString("move_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to moveperm mp. + /// + public static string moveperm_cmd { + get { + return ResourceManager.GetString("moveperm_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Moves permission from one position to another in Permissions list.. + /// + public static string moveperm_desc { + get { + return ResourceManager.GetString("moveperm_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}mp 2 4`. + /// + public static string moveperm_usage { + get { + return ResourceManager.GetString("moveperm_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to movesong ms. + /// + public static string movesong_cmd { + get { + return ResourceManager.GetString("movesong_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Moves a song from one position to another.. + /// + public static string movesong_desc { + get { + return ResourceManager.GetString("movesong_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ms 5>3`. + /// + public static string movesong_usage { + get { + return ResourceManager.GetString("movesong_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to mute. + /// + public static string mute_cmd { + get { + return ResourceManager.GetString("mute_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Mutes a mentioned user both from speaking and chatting.. + /// + public static string mute_desc { + get { + return ResourceManager.GetString("mute_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}mute @Someone`. + /// + public static string mute_usage { + get { + return ResourceManager.GetString("mute_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to next n. + /// + public static string next_cmd { + get { + return ResourceManager.GetString("next_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if {0}rcs or {0}rpl is enabled.. + /// + public static string next_desc { + get { + return ResourceManager.GetString("next_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}n` or `{0}n 5`. + /// + public static string next_usage { + get { + return ResourceManager.GetString("next_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to nowplaying np. + /// + public static string nowplaying_cmd { + get { + return ResourceManager.GetString("nowplaying_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows the song currently playing.. + /// + public static string nowplaying_desc { + get { + return ResourceManager.GetString("nowplaying_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}np`. + /// + public static string nowplaying_usage { + get { + return ResourceManager.GetString("nowplaying_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to nroll. + /// + public static string nroll_cmd { + get { + return ResourceManager.GetString("nroll_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rolls in a given range.. + /// + public static string nroll_desc { + get { + return ResourceManager.GetString("nroll_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}nroll 5` (rolls 0-5) or `{0}nroll 5-15`. + /// + public static string nroll_usage { + get { + return ResourceManager.GetString("nroll_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to osu. + /// + public static string osu_cmd { + get { + return ResourceManager.GetString("osu_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows osu stats for a player.. + /// + public static string osu_desc { + get { + return ResourceManager.GetString("osu_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}osu Name` or `{0}osu Name taiko`. + /// + public static string osu_usage { + get { + return ResourceManager.GetString("osu_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to osu5. + /// + public static string osu5_cmd { + get { + return ResourceManager.GetString("osu5_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Displays a user's top 5 plays.. + /// + public static string osu5_desc { + get { + return ResourceManager.GetString("osu5_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}osu5 Name`. + /// + public static string osu5_usage { + get { + return ResourceManager.GetString("osu5_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to osub. + /// + public static string osub_cmd { + get { + return ResourceManager.GetString("osub_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows information about an osu beatmap.. + /// + public static string osub_desc { + get { + return ResourceManager.GetString("osub_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}osub https://osu.ppy.sh/s/127712`. + /// + public static string osub_usage { + get { + return ResourceManager.GetString("osub_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to pause p. + /// + public static string pause_cmd { + get { + return ResourceManager.GetString("pause_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Pauses or Unpauses the song.. + /// + public static string pause_desc { + get { + return ResourceManager.GetString("pause_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}p`. + /// + public static string pause_usage { + get { + return ResourceManager.GetString("pause_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to permrole pr. + /// + public static string permrole_cmd { + get { + return ResourceManager.GetString("permrole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'.. + /// + public static string permrole_desc { + get { + return ResourceManager.GetString("permrole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}pr role`. + /// + public static string permrole_usage { + get { + return ResourceManager.GetString("permrole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to pick. + /// + public static string pick_cmd { + get { + return ResourceManager.GetString("pick_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Picks the currency planted in this channel.. + /// + public static string pick_desc { + get { + return ResourceManager.GetString("pick_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}pick`. + /// + public static string pick_usage { + get { + return ResourceManager.GetString("pick_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to place. + /// + public static string place_cmd { + get { + return ResourceManager.GetString("place_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a placeholder image of a given tag. Use `{0}placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments.. + /// + public static string place_desc { + get { + return ResourceManager.GetString("place_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}place Cage` or `{0}place steven 500 400`. + /// + public static string place_usage { + get { + return ResourceManager.GetString("place_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to placelist. + /// + public static string placelist_cmd { + get { + return ResourceManager.GetString("placelist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows the list of available tags for the `{0}place` command.. + /// + public static string placelist_desc { + get { + return ResourceManager.GetString("placelist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}placelist`. + /// + public static string placelist_usage { + get { + return ResourceManager.GetString("placelist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to plant. + /// + public static string plant_cmd { + get { + return ResourceManager.GetString("plant_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost). + /// + public static string plant_desc { + get { + return ResourceManager.GetString("plant_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}plant`. + /// + public static string plant_usage { + get { + return ResourceManager.GetString("plant_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to playlist pl. + /// + public static string playlist_cmd { + get { + return ResourceManager.GetString("playlist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queues up to 500 songs from a youtube playlist specified by a link, or keywords.. + /// + public static string playlist_desc { + get { + return ResourceManager.GetString("playlist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}pl playlist link or name`. + /// + public static string playlist_usage { + get { + return ResourceManager.GetString("playlist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to playlists pls. + /// + public static string playlists_cmd { + get { + return ResourceManager.GetString("playlists_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists all playlists. Paginated. 20 per page. Default page is 0.. + /// + public static string playlists_desc { + get { + return ResourceManager.GetString("playlists_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}pls 1`. + /// + public static string playlists_usage { + get { + return ResourceManager.GetString("playlists_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to pokemon poke. + /// + public static string pokemon_cmd { + get { + return ResourceManager.GetString("pokemon_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searches for a pokemon.. + /// + public static string pokemon_desc { + get { + return ResourceManager.GetString("pokemon_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}poke Sylveon`. + /// + public static string pokemon_usage { + get { + return ResourceManager.GetString("pokemon_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to pokemonability pokeab. + /// + public static string pokemonability_cmd { + get { + return ResourceManager.GetString("pokemonability_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searches for a pokemon ability.. + /// + public static string pokemonability_desc { + get { + return ResourceManager.GetString("pokemonability_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}pokeab overgrow`. + /// + public static string pokemonability_usage { + get { + return ResourceManager.GetString("pokemonability_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to poll. + /// + public static string poll_cmd { + get { + return ResourceManager.GetString("poll_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a poll which requires users to send the number of the voting option to the bot.. + /// + public static string poll_desc { + get { + return ResourceManager.GetString("poll_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}poll Question?;Answer1;Answ 2;A_3`. + /// + public static string poll_usage { + get { + return ResourceManager.GetString("poll_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to pollend. + /// + public static string pollend_cmd { + get { + return ResourceManager.GetString("pollend_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stops active poll on this server and prints the results in this channel.. + /// + public static string pollend_desc { + get { + return ResourceManager.GetString("pollend_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}pollend`. + /// + public static string pollend_usage { + get { + return ResourceManager.GetString("pollend_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to prune clr. + /// + public static string prune_cmd { + get { + return ResourceManager.GetString("prune_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}prune` removes all nadeko's messages in the last 100 messages.`{0}prune X` removes last X messages from the channel (up to 100)`{0}prune @Someone` removes all Someone's messages in the last 100 messages.`{0}prune @Someone X` removes last X 'Someone's' messages in the channel.. + /// + public static string prune_desc { + get { + return ResourceManager.GetString("prune_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}prune` or `{0}prune 5` or `{0}prune @Someone` or `{0}prune @Someone X`. + /// + public static string prune_usage { + get { + return ResourceManager.GetString("prune_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to publicpoll ppoll. + /// + public static string publicpoll_cmd { + get { + return ResourceManager.GetString("publicpoll_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a public poll which requires users to type a number of the voting option in the channel command is ran in.. + /// + public static string publicpoll_desc { + get { + return ResourceManager.GetString("publicpoll_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ppoll Question?;Answer1;Answ 2;A_3`. + /// + public static string publicpoll_usage { + get { + return ResourceManager.GetString("publicpoll_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to queue q yq. + /// + public static string queue_cmd { + get { + return ResourceManager.GetString("queue_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**.. + /// + public static string queue_desc { + get { + return ResourceManager.GetString("queue_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}q Dream Of Venice`. + /// + public static string queue_usage { + get { + return ResourceManager.GetString("queue_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to race. + /// + public static string race_cmd { + get { + return ResourceManager.GetString("race_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starts a new animal race.. + /// + public static string race_desc { + get { + return ResourceManager.GetString("race_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}race`. + /// + public static string race_usage { + get { + return ResourceManager.GetString("race_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to radio ra. + /// + public static string radio_cmd { + get { + return ResourceManager.GetString("radio_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: <https://streamable.com/al54>). + /// + public static string radio_desc { + get { + return ResourceManager.GetString("radio_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ra radio link here`. + /// + public static string radio_usage { + get { + return ResourceManager.GetString("radio_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to raffle. + /// + public static string raffle_cmd { + get { + return ResourceManager.GetString("raffle_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Prints a name and ID of a random user from the online list from the (optional) role.. + /// + public static string raffle_desc { + get { + return ResourceManager.GetString("raffle_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}raffle` or `{0}raffle RoleName`. + /// + public static string raffle_usage { + get { + return ResourceManager.GetString("raffle_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to randjoke rj. + /// + public static string randjoke_cmd { + get { + return ResourceManager.GetString("randjoke_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random joke from <http://tambal.azurewebsites.net/joke/random>. + /// + public static string randjoke_desc { + get { + return ResourceManager.GetString("randjoke_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rj`. + /// + public static string randjoke_usage { + get { + return ResourceManager.GetString("randjoke_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to randomcat meow. + /// + public static string randomcat_cmd { + get { + return ResourceManager.GetString("randomcat_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random cat image.. + /// + public static string randomcat_desc { + get { + return ResourceManager.GetString("randomcat_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}meow`. + /// + public static string randomcat_usage { + get { + return ResourceManager.GetString("randomcat_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to randomdog woof. + /// + public static string randomdog_cmd { + get { + return ResourceManager.GetString("randomdog_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random dog image.. + /// + public static string randomdog_desc { + get { + return ResourceManager.GetString("randomdog_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}woof`. + /// + public static string randomdog_usage { + get { + return ResourceManager.GetString("randomdog_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to remind. + /// + public static string remind_cmd { + get { + return ResourceManager.GetString("remind_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message.. + /// + public static string remind_desc { + get { + return ResourceManager.GetString("remind_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}remind me 1d5h Do something` or `{0}remind #general Start now!`. + /// + public static string remind_usage { + get { + return ResourceManager.GetString("remind_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to remindtemplate. + /// + public static string remindtemplate_cmd { + get { + return ResourceManager.GetString("remindtemplate_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind.. + /// + public static string remindtemplate_desc { + get { + return ResourceManager.GetString("remindtemplate_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}remindtemplate %user%, do %message%!`. + /// + public static string remindtemplate_usage { + get { + return ResourceManager.GetString("remindtemplate_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to remove rm. + /// + public static string remove_cmd { + get { + return ResourceManager.GetString("remove_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Remove a song by its # in the queue, or 'all' to remove whole queue.. + /// + public static string remove_desc { + get { + return ResourceManager.GetString("remove_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rm 5`. + /// + public static string remove_usage { + get { + return ResourceManager.GetString("remove_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to removeallroles rar. + /// + public static string removeallroles_cmd { + get { + return ResourceManager.GetString("removeallroles_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes all roles from a mentioned user.. + /// + public static string removeallroles_desc { + get { + return ResourceManager.GetString("removeallroles_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rar @User`. + /// + public static string removeallroles_usage { + get { + return ResourceManager.GetString("removeallroles_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to removeperm rp. + /// + public static string removeperm_cmd { + get { + return ResourceManager.GetString("removeperm_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes a permission from a given position in Permissions list.. + /// + public static string removeperm_desc { + get { + return ResourceManager.GetString("removeperm_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rp 1`. + /// + public static string removeperm_usage { + get { + return ResourceManager.GetString("removeperm_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to removeplaying rmpl repl. + /// + public static string removeplaying_cmd { + get { + return ResourceManager.GetString("removeplaying_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes a playing string on a given number.. + /// + public static string removeplaying_desc { + get { + return ResourceManager.GetString("removeplaying_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rmpl`. + /// + public static string removeplaying_usage { + get { + return ResourceManager.GetString("removeplaying_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to removerole rr. + /// + public static string removerole_cmd { + get { + return ResourceManager.GetString("removerole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes a role from a given user.. + /// + public static string removerole_desc { + get { + return ResourceManager.GetString("removerole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rr @User Admin`. + /// + public static string removerole_usage { + get { + return ResourceManager.GetString("removerole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to removestream rms. + /// + public static string removestream_cmd { + get { + return ResourceManager.GetString("removestream_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes notifications of a certain streamer on this channel.. + /// + public static string removestream_desc { + get { + return ResourceManager.GetString("removestream_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rms SomeGuy`. + /// + public static string removestream_usage { + get { + return ResourceManager.GetString("removestream_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to renamerole renr. + /// + public static string renamerole_cmd { + get { + return ResourceManager.GetString("renamerole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Renames a role. Roles you are renaming must be lower than bot's highest role.. + /// + public static string renamerole_desc { + get { + return ResourceManager.GetString("renamerole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}renr "First role" SecondRole`. + /// + public static string renamerole_usage { + get { + return ResourceManager.GetString("renamerole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to repeat. + /// + public static string repeat_cmd { + get { + return ResourceManager.GetString("repeat_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Repeat a message every X minutes. If no parameters are specified, repeat is disabled.. + /// + public static string repeat_desc { + get { + return ResourceManager.GetString("repeat_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}repeat 5 Hello there`. + /// + public static string repeat_usage { + get { + return ResourceManager.GetString("repeat_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to repeatinvoke repinv. + /// + public static string repeatinvoke_cmd { + get { + return ResourceManager.GetString("repeatinvoke_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Immediately shows the repeat message and restarts the timer.. + /// + public static string repeatinvoke_desc { + get { + return ResourceManager.GetString("repeatinvoke_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}repinv`. + /// + public static string repeatinvoke_usage { + get { + return ResourceManager.GetString("repeatinvoke_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rpeatplaylst rpl. + /// + public static string repeatpl_cmd { + get { + return ResourceManager.GetString("repeatpl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue).. + /// + public static string repeatpl_desc { + get { + return ResourceManager.GetString("repeatpl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rpl`. + /// + public static string repeatpl_usage { + get { + return ResourceManager.GetString("repeatpl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to reptcursong rcs. + /// + public static string reptcursong_cmd { + get { + return ResourceManager.GetString("reptcursong_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles repeat of current song.. + /// + public static string reptcursong_desc { + get { + return ResourceManager.GetString("reptcursong_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rcs`. + /// + public static string reptcursong_usage { + get { + return ResourceManager.GetString("reptcursong_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to resetperms. + /// + public static string resetpermissions_cmd { + get { + return ResourceManager.GetString("resetpermissions_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Resets BOT's permissions module on this server to the default value.. + /// + public static string resetpermissions_desc { + get { + return ResourceManager.GetString("resetpermissions_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}resetperms`. + /// + public static string resetpermissions_usage { + get { + return ResourceManager.GetString("resetpermissions_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to restart. + /// + public static string restart_cmd { + get { + return ResourceManager.GetString("restart_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Restarts the bot. Might not work.. + /// + public static string restart_desc { + get { + return ResourceManager.GetString("restart_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}restart`. + /// + public static string restart_usage { + get { + return ResourceManager.GetString("restart_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to revav. + /// + public static string revav_cmd { + get { + return ResourceManager.GetString("revav_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Returns a google reverse image search for someone's avatar.. + /// + public static string revav_desc { + get { + return ResourceManager.GetString("revav_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}revav "@SomeGuy"`. + /// + public static string revav_usage { + get { + return ResourceManager.GetString("revav_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to revimg. + /// + public static string revimg_cmd { + get { + return ResourceManager.GetString("revimg_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Returns a google reverse image search for an image from a link.. + /// + public static string revimg_desc { + get { + return ResourceManager.GetString("revimg_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}revimg Image link`. + /// + public static string revimg_usage { + get { + return ResourceManager.GetString("revimg_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rolecmd rc. + /// + public static string rolecmd_cmd { + get { + return ResourceManager.GetString("rolecmd_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a command's permission at the role level.. + /// + public static string rolecmd_desc { + get { + return ResourceManager.GetString("rolecmd_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rc "command name" disable MyRole`. + /// + public static string rolecmd_usage { + get { + return ResourceManager.GetString("rolecmd_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rolecolor rc. + /// + public static string rolecolor_cmd { + get { + return ResourceManager.GetString("rolecolor_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Set a role's color to the hex or 0-255 rgb color value provided.. + /// + public static string rolecolor_desc { + get { + return ResourceManager.GetString("rolecolor_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rc Admin 255 200 100` or `{0}rc Admin ffba55`. + /// + public static string rolecolor_usage { + get { + return ResourceManager.GetString("rolecolor_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rolemdl rm. + /// + public static string rolemdl_cmd { + get { + return ResourceManager.GetString("rolemdl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a module's permission at the role level.. + /// + public static string rolemdl_desc { + get { + return ResourceManager.GetString("rolemdl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rm "module name" enable MyRole`. + /// + public static string rolemdl_usage { + get { + return ResourceManager.GetString("rolemdl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to roles. + /// + public static string roles_cmd { + get { + return ResourceManager.GetString("roles_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to List roles on this server or a roles of a specific user if specified. Paginated. 20 roles per page.. + /// + public static string roles_desc { + get { + return ResourceManager.GetString("roles_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}roles 2` or `{0}roles @Someone`. + /// + public static string roles_usage { + get { + return ResourceManager.GetString("roles_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to roll. + /// + public static string roll_cmd { + get { + return ResourceManager.GetString("roll_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 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.. + /// + public static string roll_desc { + get { + return ResourceManager.GetString("roll_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}roll` or `{0}roll 7` or `{0}roll 3d5`. + /// + public static string roll_usage { + get { + return ResourceManager.GetString("roll_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rolluo. + /// + public static string rolluo_cmd { + get { + return ResourceManager.GetString("rolluo_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Rolls X normal dice (up to 30) unordered. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y.. + /// + public static string rolluo_desc { + get { + return ResourceManager.GetString("rolluo_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rolluo` or `{0}rolluo 7` or `{0}rolluo 3d5`. + /// + public static string rolluo_usage { + get { + return ResourceManager.GetString("rolluo_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rotateplaying ropl. + /// + public static string rotateplaying_cmd { + get { + return ResourceManager.GetString("rotateplaying_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles rotation of playing status of the dynamic strings you previously specified.. + /// + public static string rotateplaying_desc { + get { + return ResourceManager.GetString("rotateplaying_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ropl`. + /// + public static string rotateplaying_usage { + get { + return ResourceManager.GetString("rotateplaying_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rps. + /// + public static string rps_cmd { + get { + return ResourceManager.GetString("rps_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Play a game of rocket paperclip scissors with Nadeko.. + /// + public static string rps_desc { + get { + return ResourceManager.GetString("rps_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rps scissors`. + /// + public static string rps_usage { + get { + return ResourceManager.GetString("rps_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rsar. + /// + public static string rsar_cmd { + get { + return ResourceManager.GetString("rsar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes a specified role from the list of self-assignable roles.. + /// + public static string rsar_desc { + get { + return ResourceManager.GetString("rsar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rsar`. + /// + public static string rsar_usage { + get { + return ResourceManager.GetString("rsar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to rule34. + /// + public static string rule34_cmd { + get { + return ResourceManager.GetString("rule34_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (multiple tags are appended with +). + /// + public static string rule34_desc { + get { + return ResourceManager.GetString("rule34_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}rule34 yuri+kissing`. + /// + public static string rule34_usage { + get { + return ResourceManager.GetString("rule34_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to safebooru. + /// + public static string safebooru_cmd { + get { + return ResourceManager.GetString("safebooru_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random image from safebooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +). + /// + public static string safebooru_desc { + get { + return ResourceManager.GetString("safebooru_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}safebooru yuri+kissing`. + /// + public static string safebooru_usage { + get { + return ResourceManager.GetString("safebooru_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to save. + /// + public static string save_cmd { + get { + return ResourceManager.GetString("save_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes.. + /// + public static string save_desc { + get { + return ResourceManager.GetString("save_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}save classical1`. + /// + public static string save_usage { + get { + return ResourceManager.GetString("save_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to savechat. + /// + public static string savechat_cmd { + get { + return ResourceManager.GetString("savechat_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Saves a number of messages to a text file and sends it to you.. + /// + public static string savechat_desc { + get { + return ResourceManager.GetString("savechat_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}savechat 150`. + /// + public static string savechat_usage { + get { + return ResourceManager.GetString("savechat_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to scsc. + /// + public static string scsc_cmd { + get { + return ResourceManager.GetString("scsc_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance.. + /// + public static string scsc_desc { + get { + return ResourceManager.GetString("scsc_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}scsc`. + /// + public static string scsc_usage { + get { + return ResourceManager.GetString("scsc_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to send. + /// + public static string send_cmd { + get { + return ResourceManager.GetString("send_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prepend channel id with `c:` and user id with `u:`.. + /// + public static string send_desc { + get { + return ResourceManager.GetString("send_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}send serverid|c:channelid message` or `{0}send serverid|u:userid message`. + /// + public static string send_usage { + get { + return ResourceManager.GetString("send_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to sbl. + /// + public static string serverblacklist_cmd { + get { + return ResourceManager.GetString("serverblacklist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist.. + /// + public static string serverblacklist_desc { + get { + return ResourceManager.GetString("serverblacklist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sbl add 12312321312` or `{0}sbl rem SomeTrashServer`. + /// + public static string serverblacklist_usage { + get { + return ResourceManager.GetString("serverblacklist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to serverid sid. + /// + public static string serverid_cmd { + get { + return ResourceManager.GetString("serverid_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows current server ID.. + /// + public static string serverid_desc { + get { + return ResourceManager.GetString("serverid_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sid`. + /// + public static string serverid_usage { + get { + return ResourceManager.GetString("serverid_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to serverinfo sinfo. + /// + public static string serverinfo_cmd { + get { + return ResourceManager.GetString("serverinfo_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows info about the server the bot is on. If no channel is supplied, it defaults to current one.. + /// + public static string serverinfo_desc { + get { + return ResourceManager.GetString("serverinfo_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sinfo Some Server`. + /// + public static string serverinfo_usage { + get { + return ResourceManager.GetString("serverinfo_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to setavatar setav. + /// + public static string setavatar_cmd { + get { + return ResourceManager.GetString("setavatar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a new avatar image for the NadekoBot. Argument is a direct link to an image.. + /// + public static string setavatar_desc { + get { + return ResourceManager.GetString("setavatar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}setav http://i.imgur.com/xTG3a1I.jpg`. + /// + public static string setavatar_usage { + get { + return ResourceManager.GetString("setavatar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to setchanlname schn. + /// + public static string setchanlname_cmd { + get { + return ResourceManager.GetString("setchanlname_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Changes the name of the current channel.. + /// + public static string setchanlname_desc { + get { + return ResourceManager.GetString("setchanlname_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}schn NewName`. + /// + public static string setchanlname_usage { + get { + return ResourceManager.GetString("setchanlname_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to setgame. + /// + public static string setgame_cmd { + get { + return ResourceManager.GetString("setgame_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the bots game.. + /// + public static string setgame_desc { + get { + return ResourceManager.GetString("setgame_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}setgame with snakes`. + /// + public static string setgame_usage { + get { + return ResourceManager.GetString("setgame_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to setmaxqueue smq. + /// + public static string setmaxqueue_cmd { + get { + return ResourceManager.GetString("setmaxqueue_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a maximum queue size. Supply 0 or no argument to have no limit.. + /// + public static string setmaxqueue_desc { + get { + return ResourceManager.GetString("setmaxqueue_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}smq 50` or `{0}smq`. + /// + public static string setmaxqueue_usage { + get { + return ResourceManager.GetString("setmaxqueue_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to setmuterole. + /// + public static string setmuterole_cmd { + get { + return ResourceManager.GetString("setmuterole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute.. + /// + public static string setmuterole_desc { + get { + return ResourceManager.GetString("setmuterole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}setmuterole Silenced`. + /// + public static string setmuterole_usage { + get { + return ResourceManager.GetString("setmuterole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to setname newnm. + /// + public static string setname_cmd { + get { + return ResourceManager.GetString("setname_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gives the bot a new name.. + /// + public static string setname_desc { + get { + return ResourceManager.GetString("setname_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}newnm BotName`. + /// + public static string setname_usage { + get { + return ResourceManager.GetString("setname_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to setrole sr. + /// + public static string setrole_cmd { + get { + return ResourceManager.GetString("setrole_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a role for a given user.. + /// + public static string setrole_desc { + get { + return ResourceManager.GetString("setrole_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sr @User Guest`. + /// + public static string setrole_usage { + get { + return ResourceManager.GetString("setrole_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to setstream. + /// + public static string setstream_cmd { + get { + return ResourceManager.GetString("setstream_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the bots stream. First argument is the twitch link, second argument is stream name.. + /// + public static string setstream_desc { + get { + return ResourceManager.GetString("setstream_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}setstream TWITCHLINK Hello`. + /// + public static string setstream_usage { + get { + return ResourceManager.GetString("setstream_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to settopic st. + /// + public static string settopic_cmd { + get { + return ResourceManager.GetString("settopic_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a topic on the current channel.. + /// + public static string settopic_desc { + get { + return ResourceManager.GetString("settopic_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}st My new topic`. + /// + public static string settopic_usage { + get { + return ResourceManager.GetString("settopic_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to shorten. + /// + public static string shorten_cmd { + get { + return ResourceManager.GetString("shorten_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Attempts to shorten an URL, if it fails, returns the input URL.. + /// + public static string shorten_desc { + get { + return ResourceManager.GetString("shorten_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}shorten https://google.com`. + /// + public static string shorten_usage { + get { + return ResourceManager.GetString("shorten_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to showcustreact scr. + /// + public static string showcustreact_cmd { + get { + return ResourceManager.GetString("showcustreact_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a custom reaction's response on a given ID.. + /// + public static string showcustreact_desc { + get { + return ResourceManager.GetString("showcustreact_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}scr 1`. + /// + public static string showcustreact_usage { + get { + return ResourceManager.GetString("showcustreact_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to showemojis se. + /// + public static string showemojis_cmd { + get { + return ResourceManager.GetString("showemojis_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a name and a link to every SPECIAL emoji in the message.. + /// + public static string showemojis_desc { + get { + return ResourceManager.GetString("showemojis_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}se A message full of SPECIAL emojis`. + /// + public static string showemojis_usage { + get { + return ResourceManager.GetString("showemojis_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ... + /// + public static string showquote_cmd { + get { + return ResourceManager.GetString("showquote_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random quote with a specified name.. + /// + public static string showquote_desc { + get { + return ResourceManager.GetString("showquote_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}.. abc`. + /// + public static string showquote_usage { + get { + return ResourceManager.GetString("showquote_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to shuffle sh. + /// + public static string shuffledeck_cmd { + get { + return ResourceManager.GetString("shuffledeck_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reshuffles all cards back into the deck.. + /// + public static string shuffledeck_desc { + get { + return ResourceManager.GetString("shuffledeck_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sh`. + /// + public static string shuffledeck_usage { + get { + return ResourceManager.GetString("shuffledeck_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to shuffle sh. + /// + public static string shuffleplaylist_cmd { + get { + return ResourceManager.GetString("shuffleplaylist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shuffles the current playlist.. + /// + public static string shuffleplaylist_desc { + get { + return ResourceManager.GetString("shuffleplaylist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sh`. + /// + public static string shuffleplaylist_usage { + get { + return ResourceManager.GetString("shuffleplaylist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to slowmode. + /// + public static string slowmode_cmd { + get { + return ResourceManager.GetString("slowmode_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds.. + /// + public static string slowmode_desc { + get { + return ResourceManager.GetString("slowmode_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}slowmode 1 5` or `{0}slowmode`. + /// + public static string slowmode_usage { + get { + return ResourceManager.GetString("slowmode_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to softban sb. + /// + public static string softban_cmd { + get { + return ResourceManager.GetString("softban_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Bans and then unbans a user by ID or name with an optional message.. + /// + public static string softban_desc { + get { + return ResourceManager.GetString("softban_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sb "@some Guy" Your behaviour is toxic.`. + /// + public static string softban_usage { + get { + return ResourceManager.GetString("softban_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to soundcloudpl scpl. + /// + public static string soundcloudpl_cmd { + get { + return ResourceManager.GetString("soundcloudpl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queue a soundcloud playlist using a link.. + /// + public static string soundcloudpl_desc { + get { + return ResourceManager.GetString("soundcloudpl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}scpl soundcloudseturl`. + /// + public static string soundcloudpl_usage { + get { + return ResourceManager.GetString("soundcloudpl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to soundcloudqueue sq. + /// + public static string soundcloudqueue_cmd { + get { + return ResourceManager.GetString("soundcloudqueue_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**.. + /// + public static string soundcloudqueue_desc { + get { + return ResourceManager.GetString("soundcloudqueue_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sq Dream Of Venice`. + /// + public static string soundcloudqueue_usage { + get { + return ResourceManager.GetString("soundcloudqueue_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to srvrcmd sc. + /// + public static string srvrcmd_cmd { + get { + return ResourceManager.GetString("srvrcmd_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a command's permission at the server level.. + /// + public static string srvrcmd_desc { + get { + return ResourceManager.GetString("srvrcmd_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sc "command name" disable`. + /// + public static string srvrcmd_usage { + get { + return ResourceManager.GetString("srvrcmd_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to srvrfilterinv sfi. + /// + public static string srvrfilterinv_cmd { + get { + return ResourceManager.GetString("srvrfilterinv_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles automatic deleting of invites posted in the server. Does not affect Bot Owner.. + /// + public static string srvrfilterinv_desc { + get { + return ResourceManager.GetString("srvrfilterinv_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sfi`. + /// + public static string srvrfilterinv_usage { + get { + return ResourceManager.GetString("srvrfilterinv_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to srvrfilterwords sfw. + /// + public static string srvrfilterwords_cmd { + get { + return ResourceManager.GetString("srvrfilterwords_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles automatic deleting of messages containing forbidden words on the server. Does not affect Bot Owner.. + /// + public static string srvrfilterwords_desc { + get { + return ResourceManager.GetString("srvrfilterwords_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sfw`. + /// + public static string srvrfilterwords_usage { + get { + return ResourceManager.GetString("srvrfilterwords_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to srvrmdl sm. + /// + public static string srvrmdl_cmd { + get { + return ResourceManager.GetString("srvrmdl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a module's permission at the server level.. + /// + public static string srvrmdl_desc { + get { + return ResourceManager.GetString("srvrmdl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sm "module name" enable`. + /// + public static string srvrmdl_usage { + get { + return ResourceManager.GetString("srvrmdl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to startwar sw. + /// + public static string startwar_cmd { + get { + return ResourceManager.GetString("startwar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starts a war with a given number.. + /// + public static string startwar_desc { + get { + return ResourceManager.GetString("startwar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}sw 15`. + /// + public static string startwar_usage { + get { + return ResourceManager.GetString("startwar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to stats. + /// + public static string stats_cmd { + get { + return ResourceManager.GetString("stats_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows some basic stats for Nadeko.. + /// + public static string stats_desc { + get { + return ResourceManager.GetString("stats_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}stats`. + /// + public static string stats_usage { + get { + return ResourceManager.GetString("stats_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to stop s. + /// + public static string stop_cmd { + get { + return ResourceManager.GetString("stop_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stops the music and clears the playlist. Stays in the channel.. + /// + public static string stop_desc { + get { + return ResourceManager.GetString("stop_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}s`. + /// + public static string stop_usage { + get { + return ResourceManager.GetString("stop_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to take. + /// + public static string take_cmd { + get { + return ResourceManager.GetString("take_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Takes a certain amount of currency from someone.. + /// + public static string take_desc { + get { + return ResourceManager.GetString("take_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}take 1 "@someguy"`. + /// + public static string take_usage { + get { + return ResourceManager.GetString("take_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to togglexclsar tesar. + /// + public static string tesar_cmd { + get { + return ResourceManager.GetString("tesar_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles whether the self-assigned roles are exclusive. (So that any person can have only one of the self assignable roles). + /// + public static string tesar_desc { + get { + return ResourceManager.GetString("tesar_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}tesar`. + /// + public static string tesar_usage { + get { + return ResourceManager.GetString("tesar_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to tl. + /// + public static string tl_cmd { + get { + return ResourceManager.GetString("tl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a current trivia leaderboard.. + /// + public static string tl_desc { + get { + return ResourceManager.GetString("tl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}tl`. + /// + public static string tl_usage { + get { + return ResourceManager.GetString("tl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to togethertube totube. + /// + public static string togethertube_cmd { + get { + return ResourceManager.GetString("togethertube_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a new room on <https://togethertube.com> and shows the link in the chat.. + /// + public static string togethertube_desc { + get { + return ResourceManager.GetString("togethertube_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}totube`. + /// + public static string togethertube_usage { + get { + return ResourceManager.GetString("togethertube_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to tq. + /// + public static string tq_cmd { + get { + return ResourceManager.GetString("tq_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Quits current trivia after current question.. + /// + public static string tq_desc { + get { + return ResourceManager.GetString("tq_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}tq`. + /// + public static string tq_usage { + get { + return ResourceManager.GetString("tq_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to translangs. + /// + public static string translangs_cmd { + get { + return ResourceManager.GetString("translangs_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists the valid languages for translation.. + /// + public static string translangs_desc { + get { + return ResourceManager.GetString("translangs_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}translangs`. + /// + public static string translangs_usage { + get { + return ResourceManager.GetString("translangs_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to translate trans. + /// + public static string translate_cmd { + get { + return ResourceManager.GetString("translate_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Translates from>to text. From the given language to the destination language.. + /// + public static string translate_desc { + get { + return ResourceManager.GetString("translate_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}trans en>fr Hello`. + /// + public static string translate_usage { + get { + return ResourceManager.GetString("translate_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to trivia t. + /// + public static string trivia_cmd { + get { + return ResourceManager.GetString("trivia_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question.. + /// + public static string trivia_desc { + get { + return ResourceManager.GetString("trivia_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}t` or `{0}t 5 nohint`. + /// + public static string trivia_usage { + get { + return ResourceManager.GetString("trivia_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to twitch tw. + /// + public static string twitch_cmd { + get { + return ResourceManager.GetString("twitch_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Notifies this channel when a certain user starts streaming.. + /// + public static string twitch_desc { + get { + return ResourceManager.GetString("twitch_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}twitch SomeStreamer`. + /// + public static string twitch_usage { + get { + return ResourceManager.GetString("twitch_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to typeadd. + /// + public static string typeadd_cmd { + get { + return ResourceManager.GetString("typeadd_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Adds a new article to the typing contest.. + /// + public static string typeadd_desc { + get { + return ResourceManager.GetString("typeadd_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}typeadd wordswords`. + /// + public static string typeadd_usage { + get { + return ResourceManager.GetString("typeadd_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to typedel. + /// + public static string typedel_cmd { + get { + return ResourceManager.GetString("typedel_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deletes a typing article given the ID.. + /// + public static string typedel_desc { + get { + return ResourceManager.GetString("typedel_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}typedel 3`. + /// + public static string typedel_usage { + get { + return ResourceManager.GetString("typedel_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to typelist. + /// + public static string typelist_cmd { + get { + return ResourceManager.GetString("typelist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Lists added typing articles with their IDs. 15 per page.. + /// + public static string typelist_desc { + get { + return ResourceManager.GetString("typelist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}typelist` or `{0}typelist 3`. + /// + public static string typelist_usage { + get { + return ResourceManager.GetString("typelist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to typestart. + /// + public static string typestart_cmd { + get { + return ResourceManager.GetString("typestart_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starts a typing contest.. + /// + public static string typestart_desc { + get { + return ResourceManager.GetString("typestart_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}typestart`. + /// + public static string typestart_usage { + get { + return ResourceManager.GetString("typestart_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to typestop. + /// + public static string typestop_cmd { + get { + return ResourceManager.GetString("typestop_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Stops a typing contest on the current channel.. + /// + public static string typestop_desc { + get { + return ResourceManager.GetString("typestop_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}typestop`. + /// + public static string typestop_usage { + get { + return ResourceManager.GetString("typestop_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to unclaim ucall uc. + /// + public static string unclaim_cmd { + get { + return ResourceManager.GetString("unclaim_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim. + /// + public static string unclaim_desc { + get { + return ResourceManager.GetString("unclaim_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}uc [war_number] [optional_other_name]`. + /// + public static string unclaim_usage { + get { + return ResourceManager.GetString("unclaim_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to undeafen undef. + /// + public static string undeafen_cmd { + get { + return ResourceManager.GetString("undeafen_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Undeafens mentioned user or users.. + /// + public static string undeafen_desc { + get { + return ResourceManager.GetString("undeafen_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}undef "@Someguy"` or `{0}undef "@Someguy" "@Someguy"`. + /// + public static string undeafen_usage { + get { + return ResourceManager.GetString("undeafen_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to unmute. + /// + public static string unmute_cmd { + get { + return ResourceManager.GetString("unmute_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unmutes a mentioned user previously muted with `{0}mute` command.. + /// + public static string unmute_desc { + get { + return ResourceManager.GetString("unmute_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}unmute @Someone`. + /// + public static string unmute_usage { + get { + return ResourceManager.GetString("unmute_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to unstuck. + /// + public static string unstuck_cmd { + get { + return ResourceManager.GetString("unstuck_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Clears the message queue.. + /// + public static string unstuck_desc { + get { + return ResourceManager.GetString("unstuck_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}unstuck`. + /// + public static string unstuck_usage { + get { + return ResourceManager.GetString("unstuck_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to urbandict ud. + /// + public static string urbandict_cmd { + get { + return ResourceManager.GetString("urbandict_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searches Urban Dictionary for a word.. + /// + public static string urbandict_desc { + get { + return ResourceManager.GetString("urbandict_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ud Pineapple`. + /// + public static string urbandict_usage { + get { + return ResourceManager.GetString("urbandict_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ubl. + /// + public static string userblacklist_cmd { + get { + return ResourceManager.GetString("userblacklist_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist.. + /// + public static string userblacklist_desc { + get { + return ResourceManager.GetString("userblacklist_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ubl add @SomeUser` or `{0}ubl rem 12312312313`. + /// + public static string userblacklist_usage { + get { + return ResourceManager.GetString("userblacklist_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to userid uid. + /// + public static string userid_cmd { + get { + return ResourceManager.GetString("userid_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows user ID.. + /// + public static string userid_desc { + get { + return ResourceManager.GetString("userid_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}uid` or `{0}uid "@SomeGuy"`. + /// + public static string userid_usage { + get { + return ResourceManager.GetString("userid_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to userinfo uinfo. + /// + public static string userinfo_cmd { + get { + return ResourceManager.GetString("userinfo_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows info about the user. If no user is supplied, it defaults a user running the command.. + /// + public static string userinfo_desc { + get { + return ResourceManager.GetString("userinfo_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}uinfo @SomeUser`. + /// + public static string userinfo_usage { + get { + return ResourceManager.GetString("userinfo_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to userpresence. + /// + public static string userpresence_cmd { + get { + return ResourceManager.GetString("userpresence_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Starts logging to this channel when someone from the server goes online/offline/idle.. + /// + public static string userpresence_desc { + get { + return ResourceManager.GetString("userpresence_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}userpresence`. + /// + public static string userpresence_usage { + get { + return ResourceManager.GetString("userpresence_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to usrcmd uc. + /// + public static string usrcmd_cmd { + get { + return ResourceManager.GetString("usrcmd_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a command's permission at the user level.. + /// + public static string usrcmd_desc { + get { + return ResourceManager.GetString("usrcmd_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}uc "command name" enable SomeUsername`. + /// + public static string usrcmd_usage { + get { + return ResourceManager.GetString("usrcmd_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to usrmdl um. + /// + public static string usrmdl_cmd { + get { + return ResourceManager.GetString("usrmdl_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets a module's permission at the user level.. + /// + public static string usrmdl_desc { + get { + return ResourceManager.GetString("usrmdl_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}um "module name" enable SomeUsername`. + /// + public static string usrmdl_usage { + get { + return ResourceManager.GetString("usrmdl_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to verbose v. + /// + public static string verbose_cmd { + get { + return ResourceManager.GetString("verbose_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets whether to show when a command/module is blocked.. + /// + public static string verbose_desc { + get { + return ResourceManager.GetString("verbose_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}verbose true`. + /// + public static string verbose_usage { + get { + return ResourceManager.GetString("verbose_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to videocall. + /// + public static string videocall_cmd { + get { + return ResourceManager.GetString("videocall_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 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.. + /// + public static string videocall_desc { + get { + return ResourceManager.GetString("videocall_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}videocall "@SomeGuy"`. + /// + public static string videocall_usage { + get { + return ResourceManager.GetString("videocall_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to voicemute. + /// + public static string voicemute_cmd { + get { + return ResourceManager.GetString("voicemute_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Prevents a mentioned user from speaking in voice channels.. + /// + public static string voicemute_desc { + get { + return ResourceManager.GetString("voicemute_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}voicemute @Someone`. + /// + public static string voicemute_usage { + get { + return ResourceManager.GetString("voicemute_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to voice+text v+t. + /// + public static string voiceplustext_cmd { + get { + return ResourceManager.GetString("voiceplustext_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless.. + /// + public static string voiceplustext_desc { + get { + return ResourceManager.GetString("voiceplustext_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}voice+text`. + /// + public static string voiceplustext_usage { + get { + return ResourceManager.GetString("voiceplustext_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to voicepresence. + /// + public static string voicepresence_cmd { + get { + return ResourceManager.GetString("voicepresence_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Toggles logging to this channel whenever someone joins or leaves a voice channel you are currently in.. + /// + public static string voicepresence_desc { + get { + return ResourceManager.GetString("voicepresence_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}voicepresence`. + /// + public static string voicepresence_usage { + get { + return ResourceManager.GetString("voicepresence_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to voiceunmute. + /// + public static string voiceunmute_cmd { + get { + return ResourceManager.GetString("voiceunmute_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gives a previously voice-muted user a permission to speak.. + /// + public static string voiceunmute_desc { + get { + return ResourceManager.GetString("voiceunmute_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}voiceunmute @Someguy`. + /// + public static string voiceunmute_usage { + get { + return ResourceManager.GetString("voiceunmute_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to volume vol. + /// + public static string volume_cmd { + get { + return ResourceManager.GetString("volume_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sets the music volume 0-100%. + /// + public static string volume_desc { + get { + return ResourceManager.GetString("volume_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}vol 50`. + /// + public static string volume_usage { + get { + return ResourceManager.GetString("volume_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to weather we. + /// + public static string weather_cmd { + get { + return ResourceManager.GetString("weather_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations.. + /// + public static string weather_desc { + get { + return ResourceManager.GetString("weather_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}we Moscow RF`. + /// + public static string weather_usage { + get { + return ResourceManager.GetString("weather_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to whosplaying whpl. + /// + public static string whosplaying_cmd { + get { + return ResourceManager.GetString("whosplaying_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a list of users who are playing the specified game.. + /// + public static string whosplaying_desc { + get { + return ResourceManager.GetString("whosplaying_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}whpl Overwatch`. + /// + public static string whosplaying_usage { + get { + return ResourceManager.GetString("whosplaying_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to wikipedia wiki. + /// + public static string wiki_cmd { + get { + return ResourceManager.GetString("wiki_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gives you back a wikipedia link. + /// + public static string wiki_desc { + get { + return ResourceManager.GetString("wiki_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}wiki query`. + /// + public static string wiki_usage { + get { + return ResourceManager.GetString("wiki_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to wikia. + /// + public static string wikia_cmd { + get { + return ResourceManager.GetString("wikia_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Gives you back a wikia link. + /// + public static string wikia_desc { + get { + return ResourceManager.GetString("wikia_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}wikia mtg Vigilance` or `{0}wikia mlp Dashy`. + /// + public static string wikia_usage { + get { + return ResourceManager.GetString("wikia_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to wowjoke. + /// + public static string wowjoke_cmd { + get { + return ResourceManager.GetString("wowjoke_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Get one of Kwoth's penultimate WoW jokes.. + /// + public static string wowjoke_desc { + get { + return ResourceManager.GetString("wowjoke_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}wowjoke`. + /// + public static string wowjoke_usage { + get { + return ResourceManager.GetString("wowjoke_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to xkcd. + /// + public static string xkcd_cmd { + get { + return ResourceManager.GetString("xkcd_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and "latest" will get the latest one.. + /// + public static string xkcd_desc { + get { + return ResourceManager.GetString("xkcd_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}xkcd` or `{0}xkcd 1400` or `{0}xkcd latest`. + /// + public static string xkcd_usage { + get { + return ResourceManager.GetString("xkcd_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to yandere. + /// + public static string yandere_cmd { + get { + return ResourceManager.GetString("yandere_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +). + /// + public static string yandere_desc { + get { + return ResourceManager.GetString("yandere_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}yandere tag1+tag2`. + /// + public static string yandere_usage { + get { + return ResourceManager.GetString("yandere_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to yomama ym. + /// + public static string yomama_cmd { + get { + return ResourceManager.GetString("yomama_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Shows a random joke from <http://api.yomomma.info/>. + /// + public static string yomama_desc { + get { + return ResourceManager.GetString("yomama_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}ym`. + /// + public static string yomama_usage { + get { + return ResourceManager.GetString("yomama_usage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to youtube yt. + /// + public static string youtube_cmd { + get { + return ResourceManager.GetString("youtube_cmd", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Searches youtubes and shows the first result. + /// + public static string youtube_desc { + get { + return ResourceManager.GetString("youtube_desc", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `{0}yt query`. + /// + public static string youtube_usage { + get { + return ResourceManager.GetString("youtube_usage", resourceCulture); + } + } + } +} diff --git a/src/NadekoBot/Resources/CommandStrings.resx b/src/NadekoBot/Resources/CommandStrings.resx new file mode 100644 index 00000000..e2df60fe --- /dev/null +++ b/src/NadekoBot/Resources/CommandStrings.resx @@ -0,0 +1,2712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + help h + + + Either shows a help for a single command, or DMs you help link if no arguments are specified. + + + `{0}h !!q` or `{0}h` + + + hgit + + + Generates the commandlist.md file. + + + `{0}hgit` + + + donate + + + Instructions for helping the project financially. + + + `{0}donate` + + + modules mdls + + + Lists all bot modules. + + + `{0}modules` + + + commands cmds + + + List all of the bot's commands from a certain module. You can either specify full, or only first few letters of the module name. + + + `{0}commands Administration` or `{0}cmds Admin` + + + greetdel grdel + + + Sets the time it takes (in seconds) for greet messages to be auto-deleted. Set 0 to disable automatic deletion. + + + `{0}greetdel 0` or `{0}greetdel 30` + + + greet + + + Toggles anouncements on the current channel when someone joins the server. + + + `{0}greet` + + + greetmsg + + + Sets a new join announcement message which will be shown in the server's channel. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. + + + `{0}greetmsg Welcome, %user%.` + + + bye + + + Toggles anouncements on the current channel when someone leaves the server. + + + `{0}bye` + + + byemsg + + + Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. + + + `{0}byemsg %user% has left.` + + + byedel + + + Sets the time it takes (in seconds) for bye messages to be auto-deleted. Set 0 to disable automatic deletion. + + + `{0}byedel 0` or `{0}byedel 30` + + + greetdm + + + Toggles whether the greet messages will be sent in a DM (This is separate from greet - you can have both, any or neither enabled). + + + `{0}greetdm` + + + logserver + + + Logs server activity in this channel. + + + `{0}logserver` + + + logignore + + + Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. + + + `{0}logignore` + + + userpresence + + + Starts logging to this channel when someone from the server goes online/offline/idle. + + + `{0}userpresence` + + + voicepresence + + + Toggles logging to this channel whenever someone joins or leaves a voice channel you are currently in. + + + `{0}voicepresence` + + + repeatinvoke repinv + + + Immediately shows the repeat message and restarts the timer. + + + `{0}repinv` + + + repeat + + + Repeat a message every X minutes. If no parameters are specified, repeat is disabled. + + + `{0}repeat 5 Hello there` + + + rotateplaying ropl + + + Toggles rotation of playing status of the dynamic strings you previously specified. + + + `{0}ropl` + + + addplaying adpl + + + Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued% + + + `{0}adpl` + + + listplaying lipl + + + Lists all playing statuses with their corresponding number. + + + `{0}lipl` + + + removeplaying rmpl repl + + + Removes a playing string on a given number. + + + `{0}rmpl` + + + slowmode + + + Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds. + + + `{0}slowmode 1 5` or `{0}slowmode` + + + cleanvplust cv+t + + + Deletes all text channels ending in `-voice` for which voicechannels are not found. Use at your own risk. + + + `{0}cleanv+t` + + + voice+text v+t + + + Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. + + + `{0}voice+text` + + + scsc + + + Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. + + + `{0}scsc` + + + jcsc + + + Joins current channel to an instance of cross server channel using the token. + + + `{0}jcsc TokenHere` + + + lcsc + + + Leaves Cross server channel instance from this channel. + + + `{0}lcsc` + + + asar + + + Adds a role to the list of self-assignable roles. + + + `{0}asar Gamer` + + + rsar + + + Removes a specified role from the list of self-assignable roles. + + + `{0}rsar` + + + lsar + + + Lists all self-assignable roles. + + + `{0}lsar` + + + togglexclsar tesar + + + Toggles whether the self-assigned roles are exclusive. (So that any person can have only one of the self assignable roles) + + + `{0}tesar` + + + iam + + + Adds a role to you that you choose. Role must be on a list of self-assignable roles. + + + `{0}iam Gamer` + + + iamnot iamn + + + Removes a role to you that you choose. Role must be on a list of self-assignable roles. + + + `{0}iamn Gamer` + + + addcustreact acr + + + Add a custom reaction with a trigger and a response. Running this command in server requires Administration permission. Running this command in DM is Bot Owner only and adds a new global custom reaction. Guide here: <http://nadekobot.readthedocs.io/en/latest/Custom%20Reactions/> + + + `{0}acr "hello" Hi there %user%` + + + listcustreact lcr + + + Lists global or server custom reactions (20 commands per page). Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. Specifying `all` argument instead of the number will DM you a text file with a list of all custom reactions. + + + `{0}lcr 1` or `{0}lcr all` + + + listcustreactg lcrg + + + Lists global or server custom reactions (20 commands per page) grouped by trigger, and show a number of responses for each. Running the command in DM will list global custom reactions, while running it in server will list that server's custom reactions. + + + `{0}lcrg 1` + + + showcustreact scr + + + Shows a custom reaction's response on a given ID. + + + `{0}scr 1` + + + delcustreact dcr + + + Deletes a custom reaction on a specific index. If ran in DM, it is bot owner only and deletes a global custom reaction. If ran in a server, it requires Administration priviledges and removes server custom reaction. + + + `{0}dcr 5` + + + autoassignrole aar + + + Automaticaly assigns a specified role to every user who joins the server. + + + `{0}aar` to disable, `{0}aar Role Name` to enable + + + leave + + + Makes Nadeko leave the server. Either name or id required. + + + `{0}leave 123123123331` + + + delmsgoncmd + + + Toggles the automatic deletion of user's successful command message to prevent chat flood. + + + `{0}delmsgoncmd` + + + restart + + + Restarts the bot. Might not work. + + + `{0}restart` + + + setrole sr + + + Sets a role for a given user. + + + `{0}sr @User Guest` + + + removerole rr + + + Removes a role from a given user. + + + `{0}rr @User Admin` + + + renamerole renr + + + Renames a role. Roles you are renaming must be lower than bot's highest role. + + + `{0}renr "First role" SecondRole` + + + removeallroles rar + + + Removes all roles from a mentioned user. + + + `{0}rar @User` + + + createrole cr + + + Creates a role with a given name. + + + `{0}cr Awesome Role` + + + rolecolor rc + + + Set a role's color to the hex or 0-255 rgb color value provided. + + + `{0}rc Admin 255 200 100` or `{0}rc Admin ffba55` + + + ban b + + + Bans a user by ID or name with an optional message. + + + `{0}b "@some Guy" Your behaviour is toxic.` + + + softban sb + + + Bans and then unbans a user by ID or name with an optional message. + + + `{0}sb "@some Guy" Your behaviour is toxic.` + + + kick k + + + Kicks a mentioned user. + + + `{0}k "@some Guy" Your behaviour is toxic.` + + + mute + + + Mutes a mentioned user both from speaking and chatting. + + + `{0}mute @Someone` + + + voiceunmute + + + Gives a previously voice-muted user a permission to speak. + + + `{0}voiceunmute @Someguy` + + + deafen deaf + + + Deafens mentioned user or users. + + + `{0}deaf "@Someguy"` or `{0}deaf "@Someguy" "@Someguy"` + + + undeafen undef + + + Undeafens mentioned user or users. + + + `{0}undef "@Someguy"` or `{0}undef "@Someguy" "@Someguy"` + + + delvoichanl dvch + + + Deletes a voice channel with a given name. + + + `{0}dvch VoiceChannelName` + + + creatvoichanl cvch + + + Creates a new voice channel with a given name. + + + `{0}cvch VoiceChannelName` + + + deltxtchanl dtch + + + Deletes a text channel with a given name. + + + `{0}dtch TextChannelName` + + + creatxtchanl ctch + + + Creates a new text channel with a given name. + + + `{0}ctch TextChannelName` + + + settopic st + + + Sets a topic on the current channel. + + + `{0}st My new topic` + + + setchanlname schn + + + Changes the name of the current channel. + + + `{0}schn NewName` + + + prune clr + + + `{0}prune` removes all nadeko's messages in the last 100 messages.`{0}prune X` removes last X messages from the channel (up to 100)`{0}prune @Someone` removes all Someone's messages in the last 100 messages.`{0}prune @Someone X` removes last X 'Someone's' messages in the channel. + + + `{0}prune` or `{0}prune 5` or `{0}prune @Someone` or `{0}prune @Someone X` + + + die + + + Shuts the bot down. + + + `{0}die` + + + setname newnm + + + Gives the bot a new name. + + + `{0}newnm BotName` + + + setavatar setav + + + Sets a new avatar image for the NadekoBot. Argument is a direct link to an image. + + + `{0}setav http://i.imgur.com/xTG3a1I.jpg` + + + setgame + + + Sets the bots game. + + + `{0}setgame with snakes` + + + send + + + Sends a message to someone on a different server through the bot. Separate server and channel/user ids with `|` and prepend channel id with `c:` and user id with `u:`. + + + `{0}send serverid|c:channelid message` or `{0}send serverid|u:userid message` + + + mentionrole menro + + + Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. + + + `{0}menro RoleName` + + + unstuck + + + Clears the message queue. + + + `{0}unstuck` + + + donators + + + List of lovely people who donated to keep this project alive. + + + `{0}donators` + + + donadd + + + Add a donator to the database. + + + `{0}donadd Donate Amount` + + + announce + + + Sends a message to all servers' general channel bot is connected to. + + + `{0}announce Useless spam` + + + savechat + + + Saves a number of messages to a text file and sends it to you. + + + `{0}savechat 150` + + + remind + + + Sends a message to you or a channel after certain amount of time. First argument is me/here/'channelname'. Second argument is time in a descending order (mo>w>d>h>m) example: 1w5d3h10m. Third argument is a (multiword)message. + + + `{0}remind me 1d5h Do something` or `{0}remind #general Start now!` + + + remindtemplate + + + Sets message for when the remind is triggered. Available placeholders are %user% - user who ran the command, %message% - Message specified in the remind, %target% - target channel of the remind. + + + `{0}remindtemplate %user%, do %message%!` + + + serverinfo sinfo + + + Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. + + + `{0}sinfo Some Server` + + + channelinfo cinfo + + + Shows info about the channel. If no channel is supplied, it defaults to current one. + + + `{0}cinfo #some-channel` + + + userinfo uinfo + + + Shows info about the user. If no user is supplied, it defaults a user running the command. + + + `{0}uinfo @SomeUser` + + + whosplaying whpl + + + Shows a list of users who are playing the specified game. + + + `{0}whpl Overwatch` + + + 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. + + + `{0}inrole Role` + + + checkmyperms + + + Checks your user-specific permissions on this channel. + + + `{0}checkmyperms` + + + stats + + + Shows some basic stats for Nadeko. + + + `{0}stats` + + + userid uid + + + Shows user ID. + + + `{0}uid` or `{0}uid "@SomeGuy"` + + + channelid cid + + + Shows current channel ID. + + + `{0}cid` + + + serverid sid + + + Shows current server ID. + + + `{0}sid` + + + roles + + + List roles on this server or a roles of a specific user if specified. Paginated. 20 roles per page. + + + `{0}roles 2` or `{0}roles @Someone` + + + channeltopic ct + + + Sends current channel's topic as a message. + + + `{0}ct` + + + chnlfilterinv cfi + + + Toggles automatic deleting of invites posted in the channel. Does not negate the {0}srvrfilterinv enabled setting. Does not affect Bot Owner. + + + `{0}cfi` + + + srvrfilterinv sfi + + + Toggles automatic deleting of invites posted in the server. Does not affect Bot Owner. + + + `{0}sfi` + + + chnlfilterwords cfw + + + Toggles automatic deleting of messages containing banned words on the channel. Does not negate the {0}srvrfilterwords enabled setting. Does not affect bot owner. + + + `{0}cfw` + + + fw + + + Adds or removes (if it exists) a word from the list of filtered words. Use`{0}sfw` or `{0}cfw` to toggle filtering. + + + `{0}fw poop` + + + lstfilterwords lfw + + + Shows a list of filtered words. + + + `{0}lfw` + + + srvrfilterwords sfw + + + Toggles automatic deleting of messages containing forbidden words on the server. Does not affect Bot Owner. + + + `{0}sfw` + + + permrole pr + + + Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. + + + `{0}pr role` + + + verbose v + + + Sets whether to show when a command/module is blocked. + + + `{0}verbose true` + + + srvrmdl sm + + + Sets a module's permission at the server level. + + + `{0}sm "module name" enable` + + + srvrcmd sc + + + Sets a command's permission at the server level. + + + `{0}sc "command name" disable` + + + rolemdl rm + + + Sets a module's permission at the role level. + + + `{0}rm "module name" enable MyRole` + + + rolecmd rc + + + Sets a command's permission at the role level. + + + `{0}rc "command name" disable MyRole` + + + chnlmdl cm + + + Sets a module's permission at the channel level. + + + `{0}cm "module name" enable SomeChannel` + + + chnlcmd cc + + + Sets a command's permission at the channel level. + + + `{0}cc "command name" enable SomeChannel` + + + usrmdl um + + + Sets a module's permission at the user level. + + + `{0}um "module name" enable SomeUsername` + + + usrcmd uc + + + Sets a command's permission at the user level. + + + `{0}uc "command name" enable SomeUsername` + + + allsrvrmdls asm + + + Enable or disable all modules for your server. + + + `{0}asm [enable/disable]` + + + allchnlmdls acm + + + Enable or disable all modules in a specified channel. + + + `{0}acm enable #SomeChannel` + + + allrolemdls arm + + + Enable or disable all modules for a specific role. + + + `{0}arm [enable/disable] MyRole` + + + ubl + + + Either [add]s or [rem]oves a user specified by a mention or ID from a blacklist. + + + `{0}ubl add @SomeUser` or `{0}ubl rem 12312312313` + + + cbl + + + Either [add]s or [rem]oves a channel specified by an ID from a blacklist. + + + `{0}cbl rem 12312312312` + + + sbl + + + Either [add]s or [rem]oves a server specified by a Name or ID from a blacklist. + + + `{0}sbl add 12312321312` or `{0}sbl rem SomeTrashServer` + + + cmdcooldown cmdcd + + + Sets a cooldown per user for a command. Set to 0 to remove the cooldown. + + + `{0}cmdcd "some cmd" 5` + + + allcmdcooldowns acmdcds + + + Shows a list of all commands and their respective cooldowns. + + + `{0}acmdcds` + + + . + + + Adds a new quote with the specified name and message. + + + `{0}. sayhi Hi` + + + .. + + + Shows a random quote with a specified name. + + + `{0}.. abc` + + + deletequote delq + + + Deletes a random quote with the specified keyword. You have to either be server Administrator or the creator of the quote to delete it. + + + `{0}delq abc` + + + draw + + + Draws a card from the deck.If you supply number X, she draws up to 5 cards from the deck. + + + `{0}draw` or `{0}draw 5` + + + shuffle sh + + + Shuffles the current playlist. + + + `{0}sh` + + + flip + + + Flips coin(s) - heads or tails, and shows an image. + + + `{0}flip` or `{0}flip 3` + + + betflip bf + + + Bet to guess will the result be heads or tails. Guessing awards you 1.8x the currency you've bet. + + + `{0}bf 5 heads` or `{0}bf 3 t` + + + 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. + + + `{0}roll` or `{0}roll 7` or `{0}roll 3d5` + + + rolluo + + + Rolls X normal dice (up to 30) unordered. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. + + + `{0}rolluo` or `{0}rolluo 7` or `{0}rolluo 3d5` + + + nroll + + + Rolls in a given range. + + + `{0}nroll 5` (rolls 0-5) or `{0}nroll 5-15` + + + race + + + Starts a new animal race. + + + `{0}race` + + + joinrace jr + + + Joins a new race. You can specify an amount of currency for betting (optional). You will get YourBet*(participants-1) back if you win. + + + `{0}jr` or `{0}jr 5` + + + raffle + + + Prints a name and ID of a random user from the online list from the (optional) role. + + + `{0}raffle` or `{0}raffle RoleName` + + + give + + + Give someone a certain amount of currency. + + + `{0}give 1 "@SomeGuy"` + + + award + + + Awards someone a certain amount of currency. You can also specify a role name to award currency to all users in a role. + + + `{0}award 100 @person` or `{0}award 5 Role Of Gamblers` + + + take + + + Takes a certain amount of currency from someone. + + + `{0}take 1 "@someguy"` + + + betroll br + + + Bets a certain amount of currency and rolls a dice. Rolling over 66 yields x2 of your currency, over 90 - x3 and 100 x10. + + + `{0}br 5` + + + leaderboard lb + + + Displays bot currency leaderboard. + + + `{0}lb` + + + trivia t + + + Starts a game of trivia. You can add nohint to prevent hints.First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question. + + + `{0}t` or `{0}t 5 nohint` + + + tl + + + Shows a current trivia leaderboard. + + + `{0}tl` + + + tq + + + Quits current trivia after current question. + + + `{0}tq` + + + typestart + + + Starts a typing contest. + + + `{0}typestart` + + + typestop + + + Stops a typing contest on the current channel. + + + `{0}typestop` + + + typeadd + + + Adds a new article to the typing contest. + + + `{0}typeadd wordswords` + + + poll + + + Creates a poll which requires users to send the number of the voting option to the bot. + + + `{0}poll Question?;Answer1;Answ 2;A_3` + + + pollend + + + Stops active poll on this server and prints the results in this channel. + + + `{0}pollend` + + + pick + + + Picks the currency planted in this channel. + + + `{0}pick` + + + plant + + + Spend a unit of currency to plant it in this channel. (If bot is restarted or crashes, the currency will be lost) + + + `{0}plant` + + + gencurrency gc + + + Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%) + + + `{0}gc` + + + leet + + + Converts a text to leetspeak with 6 (1-6) severity levels + + + `{0}leet 3 Hello` + + + choose + + + Chooses a thing from a list of things + + + `{0}choose Get up;Sleep;Sleep more` + + + 8ball + + + Ask the 8ball a yes/no question. + + + `{0}8ball should I do something` + + + rps + + + Play a game of rocket paperclip scissors with Nadeko. + + + `{0}rps scissors` + + + linux + + + Prints a customizable Linux interjection + + + `{0}linux Spyware Windows` + + + next n + + + Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if {0}rcs or {0}rpl is enabled. + + + `{0}n` or `{0}n 5` + + + stop s + + + Stops the music and clears the playlist. Stays in the channel. + + + `{0}s` + + + destroy d + + + Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) + + + `{0}d` + + + pause p + + + Pauses or Unpauses the song. + + + `{0}p` + + + queue q yq + + + Queue a song using keywords or a link. Bot will join your voice channel.**You must be in a voice channel**. + + + `{0}q Dream Of Venice` + + + soundcloudqueue sq + + + Queue a soundcloud song using keywords. Bot will join your voice channel.**You must be in a voice channel**. + + + `{0}sq Dream Of Venice` + + + listqueue lq + + + Lists 15 currently queued songs per page. Default page is 1. + + + `{0}lq` or `{0}lq 2` + + + nowplaying np + + + Shows the song currently playing. + + + `{0}np` + + + volume vol + + + Sets the music volume 0-100% + + + `{0}vol 50` + + + defvol dv + + + Sets the default music volume when music playback is started (0-100). Persists through restarts. + + + `{0}dv 80` + + + max + + + Sets the music volume to 100%. + + + `{0}max` + + + half + + + Sets the music volume to 50%. + + + `{0}half` + + + playlist pl + + + Queues up to 500 songs from a youtube playlist specified by a link, or keywords. + + + `{0}pl playlist link or name` + + + soundcloudpl scpl + + + Queue a soundcloud playlist using a link. + + + `{0}scpl soundcloudseturl` + + + localplaylst lopl + + + Queues all songs from a directory. + + + `{0}lopl C:/music/classical` + + + radio ra + + + Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: <https://streamable.com/al54>) + + + `{0}ra radio link here` + + + local lo + + + Queues a local file by specifying a full path. + + + `{0}lo C:/music/mysong.mp3` + + + move mv + + + Moves the bot to your voice channel. (works only if music is already playing) + + + `{0}mv` + + + remove rm + + + Remove a song by its # in the queue, or 'all' to remove whole queue. + + + `{0}rm 5` + + + movesong ms + + + Moves a song from one position to another. + + + `{0}ms 5>3` + + + setmaxqueue smq + + + Sets a maximum queue size. Supply 0 or no argument to have no limit. + + + `{0}smq 50` or `{0}smq` + + + cleanup + + + Cleans up hanging voice connections. + + + `{0}cleanup` + + + reptcursong rcs + + + Toggles repeat of current song. + + + `{0}rcs` + + + rpeatplaylst rpl + + + Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). + + + `{0}rpl` + + + save + + + Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. + + + `{0}save classical1` + + + load + + + Loads a playlist under a certain name. + + + `{0}load classical-1` + + + playlists pls + + + Lists all playlists. Paginated. 20 per page. Default page is 0. + + + `{0}pls 1` + + + deleteplaylist delpls + + + Deletes a saved playlist. Only if you made it or if you are the bot owner. + + + `{0}delpls animu-5` + + + goto + + + Goes to a specific time in seconds in a song. + + + `{0}goto 30` + + + getlink gl + + + Shows a link to the song in the queue by index, or the currently playing song by default. + + + `{0}gl` + + + 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) + + + `{0}ap` + + + lolchamp + + + Shows League Of Legends champion statistics. If there are spaces/apostrophes or in the name - omit them. Optional second parameter is a role. + + + `{0}lolchamp Riven` or `{0}lolchamp Annie sup` + + + lolban + + + Shows top banned champions ordered by ban rate. + + + `{0}lolban` + + + hitbox hb + + + Notifies this channel when a certain user starts streaming. + + + `{0}hitbox SomeStreamer` + + + twitch tw + + + Notifies this channel when a certain user starts streaming. + + + `{0}twitch SomeStreamer` + + + beam bm + + + Notifies this channel when a certain user starts streaming. + + + `{0}beam SomeStreamer` + + + removestream rms + + + Removes notifications of a certain streamer on this channel. + + + `{0}rms SomeGuy` + + + liststreams ls + + + Lists all streams you are following on this server. + + + `{0}ls` + + + convert + + + Convert quantities. Use `{0}convertlist` to see supported dimensions and currencies. + + + `{0}convert m km 1000` + + + convertlist + + + List of the convertible dimensions and currencies. + + + `{0}convertlist` + + + wowjoke + + + Get one of Kwoth's penultimate WoW jokes. + + + `{0}wowjoke` + + + calculate calc + + + Evaluate a mathematical expression. + + + `{0}calc 1+1` + + + osu + + + Shows osu stats for a player. + + + `{0}osu Name` or `{0}osu Name taiko` + + + osub + + + Shows information about an osu beatmap. + + + `{0}osub https://osu.ppy.sh/s/127712` + + + osu5 + + + Displays a user's top 5 plays. + + + `{0}osu5 Name` + + + pokemon poke + + + Searches for a pokemon. + + + `{0}poke Sylveon` + + + pokemonability pokeab + + + Searches for a pokemon ability. + + + `{0}pokeab overgrow` + + + memelist + + + Pulls a list of memes you can use with `{0}memegen` from http://memegen.link/templates/ + + + `{0}memelist` + + + memegen + + + Generates a meme from memelist with top and bottom text. + + + `{0}memegen biw "gets iced coffee" "in the winter"` + + + weather we + + + Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. + + + `{0}we Moscow RF` + + + youtube yt + + + Searches youtubes and shows the first result + + + `{0}yt query` + + + anime ani aq + + + Queries anilist for an anime and shows the first result. + + + `{0}ani aquarion evol` + + + imdb omdb + + + Queries omdb for movies or series, show first result. + + + `{0}imdb Batman vs Superman` + + + manga mang mq + + + Queries anilist for a manga and shows the first result. + + + `{0}mq Shingeki no kyojin` + + + randomcat meow + + + Shows a random cat image. + + + `{0}meow` + + + randomdog woof + + + Shows a random dog image. + + + `{0}woof` + + + img i + + + Pulls the first image found using a search parameter. Use {0}ir for different results. + + + `{0}i cute kitten` + + + ir + + + Pulls a random image using a search parameter. + + + `{0}ir cute kitten` + + + lmgtfy + + + Google something for an idiot. + + + `{0}lmgtfy query` + + + google g + + + Get a google search link for some terms. + + + `{0}google query` + + + hearthstone hs + + + Searches for a Hearthstone card and shows its image. Takes a while to complete. + + + `{0}hs Ysera` + + + urbandict ud + + + Searches Urban Dictionary for a word. + + + `{0}ud Pineapple` + + + # + + + Searches Tagdef.com for a hashtag. + + + `{0}# ff` + + + catfact + + + Shows a random catfact from <http://catfacts-api.appspot.com/api/facts> + + + `{0}catfact` + + + yomama ym + + + Shows a random joke from <http://api.yomomma.info/> + + + `{0}ym` + + + randjoke rj + + + Shows a random joke from <http://tambal.azurewebsites.net/joke/random> + + + `{0}rj` + + + chucknorris cn + + + Shows a random chucknorris joke from <http://tambal.azurewebsites.net/joke/random> + + + `{0}cn` + + + magicitem mi + + + Shows a random magicitem from <https://1d4chan.org/wiki/List_of_/tg/%27s_magic_items> + + + `{0}mi` + + + revav + + + Returns a google reverse image search for someone's avatar. + + + `{0}revav "@SomeGuy"` + + + revimg + + + Returns a google reverse image search for an image from a link. + + + `{0}revimg Image link` + + + safebooru + + + Shows a random image from safebooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) + + + `{0}safebooru yuri+kissing` + + + wikipedia wiki + + + Gives you back a wikipedia link + + + `{0}wiki query` + + + color clr + + + Shows you what color corresponds to that hex. + + + `{0}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. + + + `{0}videocall "@SomeGuy"` + + + avatar av + + + Shows a mentioned person's avatar. + + + `{0}av "@SomeGuy"` + + + hentai + + + Shows a hentai image from a random website (gelbooru or danbooru or konachan or atfbooru or yandere) with a given tag. Tag is optional but preferred. Only 1 tag allowed. + + + `{0}hentai yuri` + + + danbooru + + + Shows a random hentai image from danbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) + + + `{0}danbooru yuri+kissing` + + + atfbooru atf + + + Shows a random hentai image from atfbooru with a given tag. Tag is optional but preferred. + + + `{0}atfbooru yuri+kissing` + + + gelbooru + + + Shows a random hentai image from gelbooru with a given tag. Tag is optional but preferred. (multiple tags are appended with +) + + + `{0}gelbooru yuri+kissing` + + + rule34 + + + Shows a random image from rule34.xx with a given tag. Tag is optional but preferred. (multiple tags are appended with +) + + + `{0}rule34 yuri+kissing` + + + e621 + + + Shows a random hentai image from e621.net with a given tag. Tag is optional but preferred. Use spaces for multiple tags. + + + `{0}e621 yuri kissing` + + + cp + + + We all know where this will lead you to. + + + `{0}cp` + + + boobs + + + Real adult content. + + + `{0}boobs` + + + butts ass butt + + + Real adult content. + + + `{0}butts` or `{0}ass` + + + createwar cw + + + Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. + + + `{0}cw 15 The Enemy Clan` + + + startwar sw + + + Starts a war with a given number. + + + `{0}sw 15` + + + listwar lw + + + Shows the active war claims by a number. Shows all wars in a short way if no number is specified. + + + `{0}lw [war_number] or {0}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. + + + `{0}call [war_number] [base_number] [optional_other_name]` + + + claimfinish cf + + + Finish your claim with 3 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. + + + `{0}cf 1` or `{0}cf 1 5` + + + claimfinish2 cf2 + + + Finish your claim with 2 stars if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. + + + `{0}cf2 1` or `{0}cf2 1 5` + + + claimfinish1 cf1 + + + Finish your claim with 1 star if you destroyed a base. First argument is the war number, optional second argument is a base number if you want to finish for someone else. + + + `{0}cf1 1` or `{0}cf1 1 5` + + + unclaim ucall uc + + + Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim + + + `{0}uc [war_number] [optional_other_name]` + + + endwar ew + + + Ends the war with a given index. + + + `{0}ew [war_number]` + + + translate trans + + + Translates from>to text. From the given language to the destination language. + + + `{0}trans en>fr Hello` + + + translangs + + + Lists the valid languages for translation. + + + `{0}translangs` + + + Sends a readme and a guide links to the channel. + + + `{0}readme` or `{0}guide` + + + readme guide + + + Shows all available operations in {0}calc command + + + `{0}calcops` + + + calcops + + + Deletes all quotes on a specified keyword. + + + `{0}delallq kek` + + + delallq daq + + + greetdmmsg + + + `{0}greetdmmsg Welcome to the server, %user%`. + + + Sets a new join announcement message which will be sent to the user who joined. Type %user% if you want to mention the new member. Using it with no message will show the current DM greet message. + + + Check how much currency a person has. (Defaults to yourself) + + + `{0}$$` or `{0}$$ @SomeGuy` + + + cash $$ + + + Lists whole permission chain with their indexes. You can specify an optional page number if there are a lot of permissions. + + + `{0}lp` or `{0}lp 3` + + + listperms lp + + + Enable or disable all modules for a specific user. + + + `{0}aum enable @someone` + + + allusrmdls aum + + + Moves permission from one position to another in Permissions list. + + + `{0}mp 2 4` + + + moveperm mp + + + Removes a permission from a given position in Permissions list. + + + `{0}rp 1` + + + removeperm rp + + + Migrate data from old bot configuration + + + `{0}migratedata` + + + migratedata + + + Checks if a user is online on a certain streaming platform. + + + `{0}cs twitch MyFavStreamer` + + + checkstream cs + + + showemojis se + + + Shows a name and a link to every SPECIAL emoji in the message. + + + `{0}se A message full of SPECIAL emojis` + + + shuffle sh + + + Reshuffles all cards back into the deck. + + + `{0}sh` + + + fwmsgs + + + Toggles forwarding of non-command messages sent to bot's DM to the bot owners + + + `{0}fwmsgs` + + + fwtoall + + + Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json + + + `{0}fwtoall` + + + resetperms + + + Resets BOT's permissions module on this server to the default value. + + + `{0}resetperms` + + + antiraid + + + Sets an anti-raid protection on the server. First argument is number of people which will trigger the protection. Second one is a time interval in which that number of people needs to join in order to trigger the protection, and third argument is punishment for those people (Kick, Ban, Mute) + + + `{0}antiraid 5 20 Kick` + + + antispam + + + Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. + + + `{0}antispam 3 Mute` or `{0}antispam 4 Kick` or `{0}antispam 6 Ban` + + + chatmute + + + Prevents a mentioned user from chatting in text channels. + + + `{0}chatmute @Someone` + + + voicemute + + + Prevents a mentioned user from speaking in voice channels. + + + `{0}voicemute @Someone` + + + konachan + + + Shows a random hentai image from konachan with a given tag. Tag is optional but preferred. + + + `{0}konachan yuri` + + + setmuterole + + + Sets a name of the role which will be assigned to people who should be muted. Default is nadeko-mute. + + + `{0}setmuterole Silenced` + + + adsarm + + + Toggles the automatic deletion of confirmations for {0}iam and {0}iamn commands. + + + `{0}adsarm` + + + setstream + + + Sets the bots stream. First argument is the twitch link, second argument is stream name. + + + `{0}setstream TWITCHLINK Hello` + + + chatunmute + + + Removes a mute role previously set on a mentioned user with `{0}chatmute` which prevented him from chatting in text channels. + + + `{0}chatunmute @Someone` + + + unmute + + + Unmutes a mentioned user previously muted with `{0}mute` command. + + + `{0}unmute @Someone` + + + xkcd + + + Shows a XKCD comic. No arguments will retrieve random one. Number argument will retrieve a specific comic, and "latest" will get the latest one. + + + `{0}xkcd` or `{0}xkcd 1400` or `{0}xkcd latest` + + + placelist + + + Shows the list of available tags for the `{0}place` command. + + + `{0}placelist` + + + place + + + Shows a placeholder image of a given tag. Use `{0}placelist` to see all available tags. You can specify the width and height of the image as the last two optional arguments. + + + `{0}place Cage` or `{0}place steven 500 400` + + + togethertube totube + + + Creates a new room on <https://togethertube.com> and shows the link in the chat. + + + `{0}totube` + + + publicpoll ppoll + + + Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. + + + `{0}ppoll Question?;Answer1;Answ 2;A_3` + + + autotranslang atl + + + `{0}atl en>fr` + + + Sets your source and target language to be used with `{0}at`. Specify no arguments to remove previously set value. + + + autotrans at + + + Starts automatic translation of all messages by users who set their `{0}atl` in this channel. You can set "del" argument to automatically delete all translated user messages. + + + `{0}at` or `{0}at del` + + + listquotes liqu + + + `{0}liqu` or `{0}liqu 3` + + + Lists all quotes on the server ordered alphabetically. 15 Per page. + + + typedel + + + Deletes a typing article given the ID. + + + `{0}typedel 3` + + + typelist + + + Lists added typing articles with their IDs. 15 per page. + + + `{0}typelist` or `{0}typelist 3` + + + listservers + + + Lists servers the bot is on with some basic info. 15 per page. + + + `{0}listservers 3` + + + hentaibomb + + + Shows a total 5 images (from gelbooru, danbooru, konachan, yandere and atfbooru). Tag is optional but preferred. + + + `{0}hentaibomb yuri` + + + cleverbot + + + Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. + + + `{0}cleverbot` + + + shorten + + + Attempts to shorten an URL, if it fails, returns the input URL. + + + `{0}shorten https://google.com` + + + minecraftping mcping + + + Pings a minecraft server. + + + `{0}mcping 127.0.0.1:25565` + + + minecraftquery mcq + + + Finds information about a minecraft server. + + + `{0}mcq server:ip` + + + wikia + + + Gives you back a wikia link + + + `{0}wikia mtg Vigilance` or `{0}wikia mlp Dashy` + + + bfonline bfo + + + Gives you online players for BF3 and BF4 + + + `{0}bfo bf3` or `{0}bfo bf4` + + + bfuser bfu + + + Gives you back a battlefield user's stats. + + + `{0}bfu platform game user` + + + yandere + + + Shows a random image from yandere with a given tag. Tag is optional but preferred. (multiple tags are appended with +) + + + `{0}yandere tag1+tag2` + + + magicthegathering mtg + + + Searches for a Magic The Gathering card. + + + `{0}magicthegathering about face` or `{0}mtg about face` + + \ No newline at end of file diff --git a/src/NadekoBot/Resources/ResponseStrings.Designer.cs b/src/NadekoBot/Resources/ResponseStrings.Designer.cs new file mode 100644 index 00000000..72395491 --- /dev/null +++ b/src/NadekoBot/Resources/ResponseStrings.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NadekoBot.Resources { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class ResponseStrings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + internal ResponseStrings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NadekoBot.Resources.ResponseStrings", typeof(ResponseStrings).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/src/NadekoBot/Resources/ResponseStrings.resx b/src/NadekoBot/Resources/ResponseStrings.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/src/NadekoBot/Resources/ResponseStrings.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/ChatterBot.cs b/src/NadekoBot/Services/CleverBotApi/ChatterBot.cs new file mode 100644 index 00000000..746b44aa --- /dev/null +++ b/src/NadekoBot/Services/CleverBotApi/ChatterBot.cs @@ -0,0 +1,25 @@ + /* + ChatterBotAPI + Copyright (C) 2011 pierredavidbelanger@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +namespace Services.CleverBotApi +{ + public interface ChatterBot + { + ChatterBotSession CreateSession(); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs b/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs new file mode 100644 index 00000000..36589fe6 --- /dev/null +++ b/src/NadekoBot/Services/CleverBotApi/ChatterBotFactory.cs @@ -0,0 +1,45 @@ +using System; + +/* + ChatterBotAPI + Copyright (C) 2011 pierredavidbelanger@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +namespace Services.CleverBotApi +{ + public class ChatterBotFactory + { + public static ChatterBot Create(ChatterBotType type) + { + return Create(type, null); + } + + public static ChatterBot Create(ChatterBotType type, object arg) + { + switch (type) + { + case ChatterBotType.CLEVERBOT: + return new Cleverbot("http://www.cleverbot.com/", "http://www.cleverbot.com/webservicemin?uc=321", 26); + case ChatterBotType.JABBERWACKY: + return new Cleverbot("http://jabberwacky.com", "http://jabberwacky.com/webservicemin", 20); + case ChatterBotType.PANDORABOTS: + if (arg == null) throw new ArgumentException("PANDORABOTS needs a botid arg", nameof(arg)); + return new Pandorabots(arg.ToString()); + } + return null; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/ChatterBotSession.cs b/src/NadekoBot/Services/CleverBotApi/ChatterBotSession.cs new file mode 100644 index 00000000..0f063571 --- /dev/null +++ b/src/NadekoBot/Services/CleverBotApi/ChatterBotSession.cs @@ -0,0 +1,28 @@ +/* + ChatterBotAPI + Copyright (C) 2011 pierredavidbelanger@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +using System.Threading.Tasks; + +namespace Services.CleverBotApi +{ + public interface ChatterBotSession + { + Task Think(ChatterBotThought thought); + Task Think(string text); + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/ChatterBotThought.cs b/src/NadekoBot/Services/CleverBotApi/ChatterBotThought.cs new file mode 100644 index 00000000..1a385642 --- /dev/null +++ b/src/NadekoBot/Services/CleverBotApi/ChatterBotThought.cs @@ -0,0 +1,26 @@ +/* + ChatterBotAPI + Copyright (C) 2011 pierredavidbelanger@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +namespace Services.CleverBotApi +{ + public class ChatterBotThought + { + public string[] Emotions { get; set; } + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/ChatterBotType.cs b/src/NadekoBot/Services/CleverBotApi/ChatterBotType.cs new file mode 100644 index 00000000..e4e8fab8 --- /dev/null +++ b/src/NadekoBot/Services/CleverBotApi/ChatterBotType.cs @@ -0,0 +1,27 @@ +/* + ChatterBotAPI + Copyright (C) 2011 pierredavidbelanger@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +namespace Services.CleverBotApi +{ + public enum ChatterBotType + { + CLEVERBOT, + JABBERWACKY, + PANDORABOTS + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/Cleverbot.cs b/src/NadekoBot/Services/CleverBotApi/Cleverbot.cs new file mode 100644 index 00000000..5599d7b0 --- /dev/null +++ b/src/NadekoBot/Services/CleverBotApi/Cleverbot.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; + +/* + ChatterBotAPI + Copyright (C) 2011 pierredavidbelanger@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +namespace Services.CleverBotApi +{ + public class Cleverbot : ChatterBot + { + private readonly int endIndex; + private readonly string baseUrl; + private readonly string url; + + public Cleverbot(string baseUrl, string url, int endIndex) + { + this.baseUrl = baseUrl; + this.url = url; + this.endIndex = endIndex; + } + + public ChatterBotSession CreateSession() + { + return new CleverbotSession(baseUrl, url, endIndex); + } + } + + public class CleverbotSession : ChatterBotSession + { + private readonly int endIndex; + private readonly string url; + private readonly IDictionary vars; + private readonly CookieCollection cookies; + + public CleverbotSession(string baseUrl, string url, int endIndex) + { + this.url = url; + this.endIndex = endIndex; + vars = new Dictionary(); + //vars["start"] = "y"; + vars["stimulus"] = ""; + vars["islearning"] = "1"; + vars["icognoid"] = "wsf"; + //vars["fno"] = "0"; + //vars["sub"] = "Say"; + //vars["cleanslate"] = "false"; + cookies = Utils.GetCookies(baseUrl); + } + + public async Task Think(ChatterBotThought thought) + { + vars["stimulus"] = thought.Text; + + var formData = Utils.ParametersToWWWFormURLEncoded(vars); + var formDataToDigest = formData.Substring(9, endIndex); + var formDataDigest = Utils.MD5(formDataToDigest); + vars["icognocheck"] = formDataDigest; + + var response = await Utils.Post(url, vars, cookies).ConfigureAwait(false); + + var responseValues = response.Split('\r'); + + //vars[""] = Utils.StringAtIndex(responseValues, 0); ?? + vars["sessionid"] = Utils.StringAtIndex(responseValues, 1); + vars["logurl"] = Utils.StringAtIndex(responseValues, 2); + vars["vText8"] = Utils.StringAtIndex(responseValues, 3); + vars["vText7"] = Utils.StringAtIndex(responseValues, 4); + vars["vText6"] = Utils.StringAtIndex(responseValues, 5); + vars["vText5"] = Utils.StringAtIndex(responseValues, 6); + vars["vText4"] = Utils.StringAtIndex(responseValues, 7); + vars["vText3"] = Utils.StringAtIndex(responseValues, 8); + vars["vText2"] = Utils.StringAtIndex(responseValues, 9); + vars["prevref"] = Utils.StringAtIndex(responseValues, 10); + //vars[""] = Utils.StringAtIndex(responseValues, 11); ?? +// vars["emotionalhistory"] = Utils.StringAtIndex(responseValues, 12); +// vars["ttsLocMP3"] = Utils.StringAtIndex(responseValues, 13); +// vars["ttsLocTXT"] = Utils.StringAtIndex(responseValues, 14); +// vars["ttsLocTXT3"] = Utils.StringAtIndex(responseValues, 15); +// vars["ttsText"] = Utils.StringAtIndex(responseValues, 16); +// vars["lineRef"] = Utils.StringAtIndex(responseValues, 17); +// vars["lineURL"] = Utils.StringAtIndex(responseValues, 18); +// vars["linePOST"] = Utils.StringAtIndex(responseValues, 19); +// vars["lineChoices"] = Utils.StringAtIndex(responseValues, 20); +// vars["lineChoicesAbbrev"] = Utils.StringAtIndex(responseValues, 21); +// vars["typingData"] = Utils.StringAtIndex(responseValues, 22); +// vars["divert"] = Utils.StringAtIndex(responseValues, 23); + + var responseThought = new ChatterBotThought(); + + responseThought.Text = Utils.StringAtIndex(responseValues, 0); + + return responseThought; + } + + public async Task Think(string text) + { + return (await Think(new ChatterBotThought {Text = text}).ConfigureAwait(false)).Text; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/Pandorabots.cs b/src/NadekoBot/Services/CleverBotApi/Pandorabots.cs new file mode 100644 index 00000000..0d1d8246 --- /dev/null +++ b/src/NadekoBot/Services/CleverBotApi/Pandorabots.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +/* + ChatterBotAPI + Copyright (C) 2011 pierredavidbelanger@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +namespace Services.CleverBotApi +{ + public class Pandorabots : ChatterBot + { + private readonly string botid; + + public Pandorabots(string botid) + { + this.botid = botid; + } + + public ChatterBotSession CreateSession() + { + return new PandorabotsSession(botid); + } + } + + public class PandorabotsSession : ChatterBotSession + { + private readonly IDictionary vars; + + public PandorabotsSession(string botid) + { + vars = new Dictionary(); + vars["botid"] = botid; + vars["custid"] = Guid.NewGuid().ToString(); + } + + public async Task Think(ChatterBotThought thought) + { + vars["input"] = thought.Text; + + var response = await Utils.Post("http://www.pandorabots.com/pandora/talk-xml", vars, null).ConfigureAwait(false); + + var responseThought = new ChatterBotThought(); + responseThought.Text = Utils.XPathSearch(response, "//result/that/text()"); + + return responseThought; + } + + public async Task Think(string text) + { + return (await Think(new ChatterBotThought {Text = text}).ConfigureAwait(false)).Text; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/CleverBotApi/Utils.cs b/src/NadekoBot/Services/CleverBotApi/Utils.cs new file mode 100644 index 00000000..8954afc6 --- /dev/null +++ b/src/NadekoBot/Services/CleverBotApi/Utils.cs @@ -0,0 +1,148 @@ +using NadekoBot.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Xml.XPath; + +/* + ChatterBotAPI + Copyright (C) 2011 pierredavidbelanger@gmail.com + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . +*/ + +namespace Services.CleverBotApi +{ + public static class Utils + { + public static string ParametersToWWWFormURLEncoded(IDictionary parameters) + { + string wwwFormUrlEncoded = null; + foreach (var parameterKey in parameters.Keys) + { + var parameterValue = parameters[parameterKey]; + var parameter = string.Format("{0}={1}", System.Uri.EscapeDataString(parameterKey), System.Uri.EscapeDataString(parameterValue)); + if (wwwFormUrlEncoded == null) + { + wwwFormUrlEncoded = parameter; + } + else + { + wwwFormUrlEncoded = string.Format("{0}&{1}", wwwFormUrlEncoded, parameter); + } + } + return wwwFormUrlEncoded; + } + + public static string MD5(string input) + { + // step 1, calculate MD5 hash from input + var md5 = System.Security.Cryptography.MD5.Create(); + var inputBytes = Encoding.ASCII.GetBytes(input); + var hash = md5.ComputeHash(inputBytes); + + // step 2, convert byte array to hex string + var sb = new StringBuilder(); + for (var i = 0; i < hash.Length; i++) + { + sb.Append(hash[i].ToString("X2")); + } + return sb.ToString(); + + } + + public static CookieCollection GetCookies(string url) + { + CookieContainer container = new CookieContainer(); + + HttpResponseMessage res; + using (var handler = new HttpClientHandler() { CookieContainer = container }) + using (var http = new HttpClient(handler)) + { + http.AddFakeHeaders(); + http.DefaultRequestHeaders.Add("ContentType", "text/html"); + res = http.GetAsync(url).GetAwaiter().GetResult(); + } + var response = res.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + + return container.GetCookies(res.RequestMessage.RequestUri); + } + + public static async Task Post(string url, IDictionary parameters, CookieCollection cookies) + { + var postData = ParametersToWWWFormURLEncoded(parameters); + var postDataBytes = Encoding.ASCII.GetBytes(postData); + + var request = (HttpWebRequest)WebRequest.Create(url); + + if (cookies != null) + { + var container = new CookieContainer(); + container.Add(new Uri(url), cookies); + request.CookieContainer = container; + } + + + request.Method = "POST"; + request.ContentType = "application/x-www-form-urlencoded"; + + using (var outputStream = await request.GetRequestStreamAsync()) + { + outputStream.Write(postDataBytes, 0, postDataBytes.Length); + outputStream.Flush(); + + var response = (HttpWebResponse)await request.GetResponseAsync(); + using (var responseStreamReader = new StreamReader(response.GetResponseStream())) + { + return responseStreamReader.ReadToEnd().Trim(); + } + } + + //HttpClientHandler handler; + //var uri = new Uri(url); + //if (cookies == null) + // handler = new HttpClientHandler(); + //else + //{ + // var cookieContainer = new CookieContainer(); + // cookieContainer.Add(uri, cookies); + // handler = new HttpClientHandler() { CookieContainer = cookieContainer }; + //} + //using (handler) + //using (var http = new HttpClient(handler)) + //{ + // var res = await http.PostAsync(url, new FormUrlEncodedContent(parameters)).ConfigureAwait(false); + // return await res.Content.ReadAsStringAsync().ConfigureAwait(false); + //} + } + + + public static string XPathSearch(string input, string expression) + { + var document = new XPathDocument(new MemoryStream(Encoding.ASCII.GetBytes(input))); + var navigator = document.CreateNavigator(); + return navigator.SelectSingleNode(expression).Value.Trim(); + } + + public static string StringAtIndex(string[] strings, int index) + { + if (index >= strings.Length) return ""; + return strings[index]; + } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/CommandHandler.cs b/src/NadekoBot/Services/CommandHandler.cs new file mode 100644 index 00000000..08aa6fbd --- /dev/null +++ b/src/NadekoBot/Services/CommandHandler.cs @@ -0,0 +1,314 @@ +using Discord.WebSocket; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Discord; +using NLog; +using System.Diagnostics; +using Discord.Commands; +using NadekoBot.Services.Database.Models; +using NadekoBot.Modules.Permissions; +using Discord.Net; +using NadekoBot.Extensions; +using static NadekoBot.Modules.Permissions.Permissions; +using NadekoBot.Modules.Help; +using static NadekoBot.Modules.Administration.Administration; +using NadekoBot.Modules.CustomReactions; +using NadekoBot.Modules.Games; + +namespace NadekoBot.Services +{ + public class IGuildUserComparer : IEqualityComparer + { + public bool Equals(IGuildUser x, IGuildUser y) + { + return x.Id == y.Id; + } + + public int GetHashCode(IGuildUser obj) + { + return obj.Id.GetHashCode(); + } + } + public class CommandHandler + { + private ShardedDiscordClient _client; + private CommandService _commandService; + private Logger _log; + + private List ownerChannels { get; set; } + + public event EventHandler CommandExecuted = delegate { }; + + public CommandHandler(ShardedDiscordClient client, CommandService commandService) + { + _client = client; + _commandService = commandService; + _log = LogManager.GetCurrentClassLogger(); + } + public async Task StartHandling() + { + ownerChannels = (await Task.WhenAll(_client.GetGuilds().SelectMany(g => g.GetUsers()) + .Where(u => NadekoBot.Credentials.OwnerIds.Contains(u.Id)) + .Distinct(new IGuildUserComparer()) + .Select(async u => { try { return await u.CreateDMChannelAsync(); } catch { return null; } }))) + .Where(ch => ch != null) + .ToList(); + + if (!ownerChannels.Any()) + _log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file."); + else + _log.Info($"Created {ownerChannels.Count} out of {NadekoBot.Credentials.OwnerIds.Length} owner message channels."); + + _client.MessageReceived += MessageReceivedHandler; + } + + private async Task MessageReceivedHandler(IMessage msg) + { + var usrMsg = msg as IUserMessage; + if (usrMsg == null) + return; + + if (usrMsg.Author.IsBot || !NadekoBot.Ready) //no bots + return; + + var guild = (msg.Channel as ITextChannel)?.Guild; + + if (guild != null && guild.OwnerId != usrMsg.Author.Id) + { + if (Permissions.FilterCommands.InviteFilteringChannels.Contains(usrMsg.Channel.Id) || + Permissions.FilterCommands.InviteFilteringServers.Contains(guild.Id)) + { + if (usrMsg.Content.IsDiscordInvite()) + { + try + { + await usrMsg.DeleteAsync().ConfigureAwait(false); + return; + } + catch (HttpException ex) + { + _log.Warn("I do not have permission to filter invites in channel with id " + usrMsg.Channel.Id, ex); + } + } + } + + var filteredWords = Permissions.FilterCommands.FilteredWordsForChannel(usrMsg.Channel.Id, guild.Id).Concat(Permissions.FilterCommands.FilteredWordsForServer(guild.Id)); + var wordsInMessage = usrMsg.Content.ToLowerInvariant().Split(' '); + if (filteredWords.Any(w => wordsInMessage.Contains(w))) + { + try + { + await usrMsg.DeleteAsync().ConfigureAwait(false); + return; + } + catch (HttpException ex) + { + _log.Warn("I do not have permission to filter words in channel with id " + usrMsg.Channel.Id, ex); + } + } + } + + BlacklistItem blacklistedItem; + if ((blacklistedItem = Permissions.BlacklistCommands.BlacklistedItems.FirstOrDefault(bi => + (bi.Type == BlacklistItem.BlacklistType.Server && bi.ItemId == guild?.Id) || + (bi.Type == BlacklistItem.BlacklistType.Channel && bi.ItemId == msg.Channel.Id) || + (bi.Type == BlacklistItem.BlacklistType.User && bi.ItemId == usrMsg.Author.Id))) != null) + { + return; + } + + try + { + var cleverbotExecuted = await Games.CleverBotCommands.TryAsk(usrMsg); + + if (cleverbotExecuted) + return; + } + catch (Exception ex) { _log.Warn(ex, "Error in cleverbot"); } + + try + { + // maybe this message is a custom reaction + var crExecuted = await CustomReactions.TryExecuteCustomReaction(usrMsg).ConfigureAwait(false); + + //if it was, don't execute the command + if (crExecuted) + return; + } + catch { } + + var throwaway = Task.Run(async () => + { + var sw = new Stopwatch(); + sw.Start(); + + try + { + var t = await ExecuteCommand(usrMsg, usrMsg.Content, guild, usrMsg.Author, MultiMatchHandling.Best); + var command = t.Item1; + var permCache = t.Item2; + var result = t.Item3; + sw.Stop(); + var channel = (usrMsg.Channel as ITextChannel); + if (result.IsSuccess) + { + CommandExecuted(this, new CommandExecutedEventArgs(usrMsg, command)); + _log.Info("Command Executed after {4}s\n\t" + + "User: {0}\n\t" + + "Server: {1}\n\t" + + "Channel: {2}\n\t" + + "Message: {3}", + usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} + (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} + (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} + usrMsg.Content, // {3} + sw.Elapsed.TotalSeconds // {4} + ); + } + else if (!result.IsSuccess && result.Error != CommandError.UnknownCommand) + { + _log.Warn("Command Errored after {5}s\n\t" + + "User: {0}\n\t" + + "Server: {1}\n\t" + + "Channel: {2}\n\t" + + "Message: {3}\n\t" + + "Error: {4}", + usrMsg.Author + " [" + usrMsg.Author.Id + "]", // {0} + (channel == null ? "PRIVATE" : channel.Guild.Name + " [" + channel.Guild.Id + "]"), // {1} + (channel == null ? "PRIVATE" : channel.Name + " [" + channel.Id + "]"), // {2} + usrMsg.Content,// {3} + result.ErrorReason, // {4} + sw.Elapsed.TotalSeconds // {5} + ); + if (guild != null && command != null && result.Error == CommandError.Exception) + { + if (permCache != null && permCache.Verbose) + try { await msg.Channel.SendMessageAsync("⚠️ " + result.ErrorReason).ConfigureAwait(false); } catch { } + } + } + else + { + if (msg.Channel is IPrivateChannel) + { + //rofl, gotta do this to prevent this message from occuring on polls + int vote; + if (int.TryParse(msg.Content, out vote)) return; + + await msg.Channel.SendMessageAsync(Help.DMHelpString).ConfigureAwait(false); + + await DMForwardCommands.HandleDMForwarding(msg, ownerChannels); + } + } + } + catch (Exception ex) + { + _log.Warn(ex, "Error in CommandHandler"); + if (ex.InnerException != null) + _log.Warn(ex.InnerException, "Inner Exception of the error in CommandHandler"); + } + }); + return; + } + + public async Task> ExecuteCommand(IUserMessage message, string input, IGuild guild, IUser user, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Best) { + var searchResult = _commandService.Search(message, input); + if (!searchResult.IsSuccess) + return new Tuple(null, null, searchResult); + + var commands = searchResult.Commands; + for (int i = commands.Count - 1; i >= 0; i--) + { + var preconditionResult = await commands[i].CheckPreconditions(message); + if (!preconditionResult.IsSuccess) + { + if (commands.Count == 1) + return new Tuple(null, null, searchResult); + else + continue; + } + + var parseResult = await commands[i].Parse(message, searchResult, preconditionResult); + if (!parseResult.IsSuccess) + { + if (parseResult.Error == CommandError.MultipleMatches) + { + TypeReaderValue[] argList, paramList; + switch (multiMatchHandling) + { + case MultiMatchHandling.Best: + argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); + paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToArray(); + parseResult = ParseResult.FromSuccess(argList, paramList); + break; + } + } + + if (!parseResult.IsSuccess) + { + if (commands.Count == 1) + return new Tuple(null, null, parseResult); + else + continue; + } + } + + var cmd = commands[i]; + bool resetCommand = cmd.Name == "ResetPermissions"; + PermissionCache pc; + if (guild != null) + { + pc = Permissions.Cache.GetOrAdd(guild.Id, (id) => + { + using (var uow = DbHandler.UnitOfWork()) + { + var config = uow.GuildConfigs.PermissionsFor(guild.Id); + return new PermissionCache() + { + Verbose = config.VerbosePermissions, + RootPermission = config.RootPermission, + PermRole = config.PermissionRole.Trim().ToLowerInvariant(), + }; + } + }); + int index; + if (!resetCommand && !pc.RootPermission.AsEnumerable().CheckPermissions(message, cmd.Text, cmd.Module.Name, out index)) + { + var returnMsg = $"Permission number #{index + 1} **{pc.RootPermission.GetAt(index).GetCommand(guild)}** is preventing this action."; + return new Tuple(cmd, pc, SearchResult.FromError(CommandError.Exception, returnMsg)); + } + + + if (cmd.Module.Source.Name == typeof(Permissions).Name) //permissions, you must have special role + { + if (!((IGuildUser)user).Roles.Any(r => r.Name.Trim().ToLowerInvariant() == pc.PermRole.Trim().ToLowerInvariant())) + { + return new Tuple(cmd, pc, SearchResult.FromError(CommandError.Exception, $"You need the **{pc.PermRole}** role in order to use permission commands.")); + } + } + } + + + if (CmdCdsCommands.HasCooldown(cmd, guild, user)) + return new Tuple(cmd, null, SearchResult.FromError(CommandError.Exception, $"That command is on cooldown for you.")); + + return new Tuple(commands[i], null, await commands[i].Execute(message, parseResult)); + } + + return new Tuple(null, null, SearchResult.FromError(CommandError.UnknownCommand, "This input does not match any overload.")); + } + } + + public class CommandExecutedEventArgs + { + public Command Command { get; } + public IUserMessage Message { get; } + + public CommandExecutedEventArgs(IUserMessage msg, Command cmd) + { + Message = msg; + Command = cmd; + } + } +} diff --git a/src/NadekoBot/Services/CurrencyHandler.cs b/src/NadekoBot/Services/CurrencyHandler.cs new file mode 100644 index 00000000..c12dffec --- /dev/null +++ b/src/NadekoBot/Services/CurrencyHandler.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading.Tasks; +using Discord; +using NadekoBot.Extensions; +using NadekoBot.Modules.Gambling; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services +{ + public static class CurrencyHandler + { + public static async Task RemoveCurrencyAsync(IGuildUser author, string reason, long amount, bool sendMessage) + { + var success = await RemoveCurrencyAsync(author.Id, reason, amount); + + if (success && sendMessage) + try { await author.SendMessageAsync($"`You lost:` {amount} {Gambling.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { } + + return success; + } + + public static async Task RemoveCurrencyAsync(ulong authorId, string reason, long amount) + { + if (amount < 0) + throw new ArgumentNullException(nameof(amount)); + + + using (var uow = DbHandler.UnitOfWork()) + { + var success = uow.Currency.TryUpdateState(authorId, -amount); + if (!success) + return false; + uow.CurrencyTransactions.Add(new CurrencyTransaction() + { + UserId = authorId, + Reason = reason, + Amount = -amount, + }); + await uow.CompleteAsync().ConfigureAwait(false); + } + + return true; + } + + public static async Task AddCurrencyAsync(IGuildUser author, string reason, long amount, bool sendMessage) + { + await AddCurrencyAsync(author.Id, reason, amount); + + if (sendMessage) + try { await author.SendMessageAsync($"`You received:` {amount} {Gambling.CurrencySign}\n`Reason:` {reason}").ConfigureAwait(false); } catch { } + } + + public static async Task AddCurrencyAsync(ulong receiverId, string reason, long amount) + { + if (amount < 0) + throw new ArgumentNullException(nameof(amount)); + + + using (var uow = DbHandler.UnitOfWork()) + { + uow.Currency.TryUpdateState(receiverId, amount); + uow.CurrencyTransactions.Add(new CurrencyTransaction() + { + UserId = receiverId, + Reason = reason, + Amount = amount, + }); + await uow.CompleteAsync(); + } + } + } +} diff --git a/src/NadekoBot/Services/Database/IUnitOfWork.cs b/src/NadekoBot/Services/Database/IUnitOfWork.cs new file mode 100644 index 00000000..66359ef2 --- /dev/null +++ b/src/NadekoBot/Services/Database/IUnitOfWork.cs @@ -0,0 +1,28 @@ +using NadekoBot.Services.Database.Repositories; +using System; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database +{ + public interface IUnitOfWork : IDisposable + { + NadekoContext _context { get; } + + IQuoteRepository Quotes { get; } + IGuildConfigRepository GuildConfigs { get; } + IDonatorsRepository Donators { get; } + IClashOfClansRepository ClashOfClans { get; } + IReminderRepository Reminders { get; } + ISelfAssignedRolesRepository SelfAssignedRoles { get; } + IBotConfigRepository BotConfig { get; } + IRepeaterRepository Repeaters { get; } + IUnitConverterRepository ConverterUnits { get; } + ICustomReactionRepository CustomReactions { get; } + ICurrencyRepository Currency { get; } + ICurrencyTransactionsRepository CurrencyTransactions { get; } + IMusicPlaylistRepository MusicPlaylists { get; } + + int Complete(); + Task CompleteAsync(); + } +} diff --git a/src/NadekoBot/Services/Database/Models/BotConfig.cs b/src/NadekoBot/Services/Database/Models/BotConfig.cs new file mode 100644 index 00000000..b20263c8 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/BotConfig.cs @@ -0,0 +1,120 @@ +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Models +{ + public class BotConfig : DbEntity + { + public HashSet Blacklist { get; set; } + public ulong BufferSize { get; set; } = 4000000; + public bool ForwardMessages { get; set; } = true; + public bool ForwardToAllOwners { get; set; } = true; + + public float CurrencyGenerationChance { get; set; } = 0.02f; + public int CurrencyGenerationCooldown { get; set; } = 10; + + public HashSet ModulePrefixes { get; set; } = new HashSet(); + + public List RotatingStatusMessages { get; set; } = new List(); + + public bool RotatingStatuses { get; set; } = false; + public string RemindMessageFormat { get; set; } = "❗⏰**I've been told to remind you to '%message%' now by %user%.**⏰❗"; + + + public string CurrencySign { get; set; } = "🌸"; + public string CurrencyName { get; set; } = "Nadeko Flower"; + public string CurrencyPluralName { get; set; } = "Nadeko Flowers"; + + public HashSet EightBallResponses { get; set; } = new HashSet(); + public HashSet RaceAnimals { get; set; } = new HashSet(); + + public string DMHelpString { get; set; } = "Type `-h` for help."; + public string HelpString { get; set; } = @"To add me to your server, use this link -> +You can use `{1}modules` command to see a list of all modules. +You can use `{1}commands ModuleName` +(for example `{1}commands Administration`) to see a list of all of the commands in that module. +For a specific command help, use `{1}h CommandName` (for example {1}h !!q) + + +**LIST OF COMMANDS CAN BE FOUND ON THIS LINK** + + + +Nadeko Support Server: https://discord.gg/0ehQwTK2RBjAxzEY"; + + public int MigrationVersion { get; set; } + } + + public class PlayingStatus :DbEntity + { + public string Status { get; set; } + } + + public class BlacklistItem : DbEntity + { + public ulong ItemId { get; set; } + public BlacklistType Type { get; set; } + + public enum BlacklistType + { + Server, + Channel, + User + } + } + + public class EightBallResponse : DbEntity + { + public string Text { get; set; } + + public override int GetHashCode() + { + return Text.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (!(obj is EightBallResponse)) + return base.Equals(obj); + + return ((EightBallResponse)obj).Text == Text; + } + } + + public class RaceAnimal : DbEntity + { + public string Icon { get; set; } + public string Name { get; set; } + + public override int GetHashCode() + { + return Icon.GetHashCode(); + } + + public override bool Equals(object obj) + { + if (!(obj is RaceAnimal)) + return base.Equals(obj); + + return ((RaceAnimal)obj).Icon == Icon; + } + } + + public class ModulePrefix : DbEntity + { + public string ModuleName { get; set; } + public string Prefix { get; set; } + + public override int GetHashCode() + { + return ModuleName.GetHashCode(); + } + + public override bool Equals(object obj) + { + if(!(obj is ModulePrefix)) + return base.Equals(obj); + + return ((ModulePrefix)obj).ModuleName == ModuleName; + } + } +} diff --git a/src/NadekoBot/Services/Database/Models/ClashCaller.cs b/src/NadekoBot/Services/Database/Models/ClashCaller.cs new file mode 100644 index 00000000..88ad4b78 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/ClashCaller.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel.DataAnnotations.Schema; + +namespace NadekoBot.Services.Database.Models +{ + public class ClashCaller : DbEntity + { + public int? SequenceNumber { get; set; } = null; + public string CallUser { get; set; } + + public DateTime TimeAdded { get; set; } + + public bool BaseDestroyed { get; set; } + + public int Stars { get; set; } = 3; + + public int ClashWarId { get; set; } + + [ForeignKey(nameof(ClashWarId))] + public ClashWar ClashWar { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/ClashWar.cs b/src/NadekoBot/Services/Database/Models/ClashWar.cs new file mode 100644 index 00000000..8322f5a3 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/ClashWar.cs @@ -0,0 +1,32 @@ +using Discord; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace NadekoBot.Services.Database.Models +{ + public class ClashWar : DbEntity + { + public enum DestroyStars + { + One, Two, Three + } + public enum StateOfWar + { + Started, Ended, Created + } + + public string EnemyClan { get; set; } + public int Size { get; set; } + public StateOfWar WarState { get; set; } = StateOfWar.Created; + public DateTime StartedAt { get; set; } + + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + + [NotMapped] + public ITextChannel Channel { get; set; } + + public List Bases { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/CommandCooldown.cs b/src/NadekoBot/Services/Database/Models/CommandCooldown.cs new file mode 100644 index 00000000..6adcf2ad --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/CommandCooldown.cs @@ -0,0 +1,8 @@ +namespace NadekoBot.Services.Database.Models +{ + public class CommandCooldown : DbEntity + { + public int Seconds { get; set; } + public string CommandName { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/ConvertUnit.cs b/src/NadekoBot/Services/Database/Models/ConvertUnit.cs new file mode 100644 index 00000000..f31c4ad3 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/ConvertUnit.cs @@ -0,0 +1,47 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations.Schema; + +namespace NadekoBot.Services.Database.Models +{ + public class ConvertUnit : DbEntity + { + public ConvertUnit() { } + [NotMapped] + private string[] _triggersValue; + [NotMapped] + public string[] Triggers + { + get + { + return _triggersValue ?? (_triggersValue = InternalTrigger.Split('|')); + } + set + { + _triggersValue = value; + InternalTrigger = string.Join("|", _triggersValue); + } + } + //protected or private? + /// + /// DO NOT CALL THIS + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public string InternalTrigger { get; set; } + public string UnitType { get; set; } + public decimal Modifier { get; set; } + + public override bool Equals(object obj) + { + var cu = obj as ConvertUnit; + if (cu == null) + return false; + return cu.UnitType == this.UnitType; + } + + public override int GetHashCode() + { + return this.UnitType.GetHashCode(); + } + } + +} diff --git a/src/NadekoBot/Services/Database/Models/Currency.cs b/src/NadekoBot/Services/Database/Models/Currency.cs new file mode 100644 index 00000000..b86e32e1 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Currency.cs @@ -0,0 +1,8 @@ +namespace NadekoBot.Services.Database.Models +{ + public class Currency : DbEntity + { + public ulong UserId { get; set; } + public long Amount { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/CurrencyTransaction.cs b/src/NadekoBot/Services/Database/Models/CurrencyTransaction.cs new file mode 100644 index 00000000..243539a3 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/CurrencyTransaction.cs @@ -0,0 +1,9 @@ +namespace NadekoBot.Services.Database.Models +{ + public class CurrencyTransaction : DbEntity + { + public long Amount { get; set; } + public string Reason { get; set; } + public ulong UserId { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/CustomReaction.cs b/src/NadekoBot/Services/Database/Models/CustomReaction.cs new file mode 100644 index 00000000..9ae3ab3b --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/CustomReaction.cs @@ -0,0 +1,23 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.RegularExpressions; + +namespace NadekoBot.Services.Database.Models +{ + public class CustomReaction : DbEntity + { + public ulong? GuildId { get; set; } + [NotMapped] + public Regex Regex { get; set; } + public string Response { get; set; } + public string Trigger { get; set; } + public bool IsRegex { get; set; } + public bool OwnerOnly { get; set; } + public override string ToString() => $"`#{Id}` `Trigger:` {Trigger}\n `Response:` {Response}"; + } + + public class ReactionResponse : DbEntity + { + public bool OwnerOnly { get; set; } + public string Text { get; set; } + } +} \ No newline at end of file diff --git a/src/NadekoBot/Services/Database/Models/DbEntity.cs b/src/NadekoBot/Services/Database/Models/DbEntity.cs new file mode 100644 index 00000000..5c7dda6b --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/DbEntity.cs @@ -0,0 +1,12 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace NadekoBot.Services.Database.Models +{ + public class DbEntity + { + [Key] + public int Id { get; set; } + public DateTime DateAdded { get; } = DateTime.UtcNow; + } +} diff --git a/src/NadekoBot/Services/Database/Models/Donator.cs b/src/NadekoBot/Services/Database/Models/Donator.cs new file mode 100644 index 00000000..da5cd390 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Donator.cs @@ -0,0 +1,9 @@ +namespace NadekoBot.Services.Database.Models +{ + public class Donator : DbEntity + { + public ulong UserId { get; set; } + public string Name { get; set; } + public int Amount { get; set; } = 0; + } +} diff --git a/src/NadekoBot/Services/Database/Models/FollowedStream.cs b/src/NadekoBot/Services/Database/Models/FollowedStream.cs new file mode 100644 index 00000000..817f003f --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/FollowedStream.cs @@ -0,0 +1,15 @@ +namespace NadekoBot.Services.Database.Models +{ + public class FollowedStream : DbEntity + { + public ulong ChannelId { get; set; } + public string Username { get; set; } + public FollowedStreamType Type { get; set; } + public ulong GuildId { get; set; } + + public enum FollowedStreamType + { + Twitch, Hitbox, Beam + } + } +} diff --git a/src/NadekoBot/Services/Database/Models/GuildConfig.cs b/src/NadekoBot/Services/Database/Models/GuildConfig.cs new file mode 100644 index 00000000..2c39b4a2 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/GuildConfig.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Models +{ + public class GuildConfig : DbEntity + { + public ulong GuildId { get; set; } + public bool DeleteMessageOnCommand { get; set; } + public ulong AutoAssignRoleId { get; set; } + //greet stuff + public bool AutoDeleteGreetMessages { get; set; } //unused + public bool AutoDeleteByeMessages { get; set; } // unused + public int AutoDeleteGreetMessagesTimer { get; set; } = 30; + public int AutoDeleteByeMessagesTimer { get; set; } = 30; + + public ulong GreetMessageChannelId { get; set; } + public ulong ByeMessageChannelId { get; set; } + + public bool SendDmGreetMessage { get; set; } + public string DmGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; + + public bool SendChannelGreetMessage { get; set; } + public string ChannelGreetMessageText { get; set; } = "Welcome to the %server% server, %user%!"; + + public bool SendChannelByeMessage { get; set; } + public string ChannelByeMessageText { get; set; } = "%user% has left!"; + + public LogSetting LogSetting { get; set; } = new LogSetting(); + + //self assignable roles + public bool ExclusiveSelfAssignedRoles { get; set; } + public bool AutoDeleteSelfAssignedRoleMessages { get; set; } + public float DefaultMusicVolume { get; set; } = 1.0f; + public bool VoicePlusTextEnabled { get; set; } + + //stream notifications + public HashSet FollowedStreams { get; set; } = new HashSet(); + + //currencyGeneration + public HashSet GenerateCurrencyChannelIds { get; set; } = new HashSet(); + + //permissions + public Permission RootPermission { get; set; } + public bool VerbosePermissions { get; set; } = true; + public string PermissionRole { get; set; } = "Nadeko"; + + public HashSet CommandCooldowns { get; set; } = new HashSet(); + + //filtering + public bool FilterInvites { get; set; } + public HashSet FilterInvitesChannelIds { get; set; } = new HashSet(); + + public bool FilterWords { get; set; } + public HashSet FilteredWords { get; set; } = new HashSet(); + public HashSet FilterWordsChannelIds { get; set; } = new HashSet(); + + public string MuteRoleName { get; set; } + public bool CleverbotEnabled { get; set; } + } + + public class FilterChannelId :DbEntity + { + public ulong ChannelId { get; set; } + } + + public class FilteredWord : DbEntity + { + public string Word { get; set; } + } + + public class GCChannelId : DbEntity + { + public ulong ChannelId { get; set; } + + public override bool Equals(object obj) + { + var gc = obj as GCChannelId; + if (gc == null) + return false; + + return gc.ChannelId == this.ChannelId; + } + + public override int GetHashCode() => + this.ChannelId.GetHashCode(); + } +} diff --git a/src/NadekoBot/Services/Database/Models/IgnoredLogChannel.cs b/src/NadekoBot/Services/Database/Models/IgnoredLogChannel.cs new file mode 100644 index 00000000..40198c7f --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/IgnoredLogChannel.cs @@ -0,0 +1,8 @@ +namespace NadekoBot.Services.Database.Models +{ + public class IgnoredLogChannel : DbEntity + { + public LogSetting LogSetting { get; set; } + public ulong ChannelId { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/LogSetting.cs b/src/NadekoBot/Services/Database/Models/LogSetting.cs new file mode 100644 index 00000000..b5e841fb --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/LogSetting.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Models +{ + public class LogSetting : DbEntity + { + public bool IsLogging { get; set; } + public ulong ChannelId { get; set; } + public HashSet IgnoredChannels { get; set; } + + public bool MessageUpdated { get; set; } = true; + public bool MessageDeleted { get; set; } = true; + + public bool UserJoined { get; set; } = true; + public bool UserLeft { get; set; } = true; + public bool UserBanned { get; set; } = true; + public bool UserUnbanned { get; set; } = true; + public bool UserUpdated { get; set; } = true; + + public bool ChannelCreated { get; set; } = true; + public bool ChannelDestroyed { get; set; } = true; + public bool ChannelUpdated { get; set; } = true; + + //userpresence + public bool LogUserPresence { get; set; } = false; + public ulong UserPresenceChannelId { get; set; } + + //voicepresence + public bool LogVoicePresence { get; set; } = false; + public ulong VoicePresenceChannelId { get; set; } + public HashSet IgnoredVoicePresenceChannelIds { get; set; } + + } +} diff --git a/src/NadekoBot/Services/Database/Models/MusicPlaylist.cs b/src/NadekoBot/Services/Database/Models/MusicPlaylist.cs new file mode 100644 index 00000000..6978c1f3 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/MusicPlaylist.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Models +{ + public class MusicPlaylist : DbEntity + { + public string Name { get; set; } + public string Author { get; set; } + public ulong AuthorId { get; set; } + public List Songs { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/Permission.cs b/src/NadekoBot/Services/Database/Models/Permission.cs new file mode 100644 index 00000000..e12d6d5c --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Permission.cs @@ -0,0 +1,74 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.Diagnostics; + +namespace NadekoBot.Services.Database.Models +{ + [DebuggerDisplay("{global::NadekoBot.Modules.Permissions.PermissionExtensions.GetCommand(this)}", Target = typeof(Permission))] + public class Permission : DbEntity + { + public Permission Previous { get; set; } = null; + public Permission Next { get; set; } = null; + + public PrimaryPermissionType PrimaryTarget { get; set; } + public ulong PrimaryTargetId { get; set; } + + public SecondaryPermissionType SecondaryTarget { get; set; } + public string SecondaryTargetName { get; set; } + + public bool State { get; set; } + + [NotMapped] + private static Permission AllowAllPerm => new Permission() + { + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.AllModules, + SecondaryTargetName = "*", + State = true, + }; + [NotMapped] + private static Permission BlockNsfwPerm => new Permission() + { + PrimaryTarget = PrimaryPermissionType.Server, + PrimaryTargetId = 0, + SecondaryTarget = SecondaryPermissionType.Module, + SecondaryTargetName = "nsfw", + State = false, + }; + + public static Permission GetDefaultRoot() + { + var root = AllowAllPerm; + var blockNsfw = BlockNsfwPerm; + + root.Previous = blockNsfw; + blockNsfw.Next = root; + + return blockNsfw; + } + + public Permission Clone() => new Permission() + { + PrimaryTarget = PrimaryTarget, + SecondaryTarget = SecondaryTarget, + PrimaryTargetId = PrimaryTargetId, + SecondaryTargetName = SecondaryTargetName, + State = State, + }; + } + + public enum PrimaryPermissionType + { + User, + Channel, + Role, + Server + } + + public enum SecondaryPermissionType + { + Module, + Command, + AllModules + } +} diff --git a/src/NadekoBot/Services/Database/Models/PlaylistSong.cs b/src/NadekoBot/Services/Database/Models/PlaylistSong.cs new file mode 100644 index 00000000..06cbb919 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/PlaylistSong.cs @@ -0,0 +1,13 @@ +using NadekoBot.Modules.Music.Classes; + +namespace NadekoBot.Services.Database.Models +{ + public class PlaylistSong : DbEntity + { + public string Provider { get; set; } + public MusicType ProviderType { get; set; } + public string Title { get; set; } + public string Uri { get; set; } + public string Query { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/Quote.cs b/src/NadekoBot/Services/Database/Models/Quote.cs new file mode 100644 index 00000000..f8a3e308 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Quote.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace NadekoBot.Services.Database.Models +{ + public class Quote : DbEntity + { + public ulong GuildId { get; set; } + [Required] + public string Keyword { get; set; } + [Required] + public string AuthorName { get; set; } + public ulong AuthorId { get; set; } + [Required] + public string Text { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/Reminder.cs b/src/NadekoBot/Services/Database/Models/Reminder.cs new file mode 100644 index 00000000..63600de9 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Reminder.cs @@ -0,0 +1,14 @@ +using System; + +namespace NadekoBot.Services.Database.Models +{ + public class Reminder : DbEntity + { + public DateTime When { get; set; } + public ulong ChannelId { get; set; } + public ulong ServerId { get; set; } + public ulong UserId { get; set; } + public string Message { get; set; } + public bool IsPrivate { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/Repeater.cs b/src/NadekoBot/Services/Database/Models/Repeater.cs new file mode 100644 index 00000000..cf887b43 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/Repeater.cs @@ -0,0 +1,12 @@ +using System; + +namespace NadekoBot.Services.Database.Models +{ + public class Repeater :DbEntity + { + public ulong GuildId { get; set; } + public ulong ChannelId { get; set; } + public string Message { get; set; } + public TimeSpan Interval { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/SelfAssignableRole.cs b/src/NadekoBot/Services/Database/Models/SelfAssignableRole.cs new file mode 100644 index 00000000..93e70223 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/SelfAssignableRole.cs @@ -0,0 +1,8 @@ +namespace NadekoBot.Services.Database.Models +{ + public class SelfAssignedRole : DbEntity + { + public ulong GuildId { get; set; } + public ulong RoleId { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/Models/VoicePresenceChannel.cs b/src/NadekoBot/Services/Database/Models/VoicePresenceChannel.cs new file mode 100644 index 00000000..75bced37 --- /dev/null +++ b/src/NadekoBot/Services/Database/Models/VoicePresenceChannel.cs @@ -0,0 +1,8 @@ +namespace NadekoBot.Services.Database.Models +{ + public class IgnoredVoicePresenceChannel : DbEntity + { + public LogSetting LogSetting { get; set; } + public ulong ChannelId { get; set; } + } +} diff --git a/src/NadekoBot/Services/Database/NadekoContext.cs b/src/NadekoBot/Services/Database/NadekoContext.cs new file mode 100644 index 00000000..da16e598 --- /dev/null +++ b/src/NadekoBot/Services/Database/NadekoContext.cs @@ -0,0 +1,224 @@ +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using NadekoBot.Services.Database.Models; +using NadekoBot.Extensions; + +namespace NadekoBot.Services.Database +{ + public class NadekoContext : DbContext + { + public DbSet Quotes { get; set; } + public DbSet Donators { get; set; } + public DbSet GuildConfigs { get; set; } + public DbSet ClashOfClans { get; set; } + public DbSet ClashCallers { get; set; } + public DbSet Reminders { get; set; } + public DbSet SelfAssignableRoles { get; set; } + public DbSet BotConfig { get; set; } + public DbSet Repeaters { get; set; } + public DbSet Currency { get; set; } + public DbSet ConversionUnits { get; set; } + public DbSet MusicPlaylists { get; set; } + public DbSet CustomReactions { get; set; } + public DbSet CurrencyTransactions { get; set; } + + //logging + public DbSet LogSettings { get; set; } + public DbSet IgnoredLogChannels { get; set; } + public DbSet IgnoredVoicePresenceCHannels { get; set; } + + //orphans xD + public DbSet EightBallResponses { get; set; } + public DbSet RaceAnimals { get; set; } + public DbSet ModulePrefixes { get; set; } + + public NadekoContext() + { + this.Database.Migrate(); + } + + public NadekoContext(DbContextOptions options) : base(options) + { + this.Database.Migrate(); + EnsureSeedData(); + } + //protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + //{ + // optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db"); + //} + + public void EnsureSeedData() + { + if (!BotConfig.Any()) + { + var bc = new BotConfig(); + + bc.ModulePrefixes.AddRange(new HashSet() + { + new ModulePrefix() { ModuleName = "Administration", Prefix = "." }, + new ModulePrefix() { ModuleName = "Searches", Prefix = "~" }, + new ModulePrefix() { ModuleName = "Translator", Prefix = "~" }, + new ModulePrefix() { ModuleName = "NSFW", Prefix = "~" }, + new ModulePrefix() { ModuleName = "ClashOfClans", Prefix = "," }, + new ModulePrefix() { ModuleName = "Help", Prefix = "-" }, + new ModulePrefix() { ModuleName = "Music", Prefix = "!!" }, + new ModulePrefix() { ModuleName = "Trello", Prefix = "trello" }, + new ModulePrefix() { ModuleName = "Games", Prefix = ">" }, + new ModulePrefix() { ModuleName = "Gambling", Prefix = "$" }, + new ModulePrefix() { ModuleName = "Permissions", Prefix = ";" }, + new ModulePrefix() { ModuleName = "Pokemon", Prefix = ">" }, + new ModulePrefix() { ModuleName = "Utility", Prefix = "." }, + new ModulePrefix() { ModuleName = "CustomReactions", Prefix = "." } + }); + bc.RaceAnimals.AddRange(new HashSet + { + new RaceAnimal { Icon = "🐼", Name = "Panda" }, + new RaceAnimal { Icon = "🐻", Name = "Bear" }, + new RaceAnimal { Icon = "🐧", Name = "Pengu" }, + new RaceAnimal { Icon = "🐨", Name = "Koala" }, + new RaceAnimal { Icon = "🐬", Name = "Dolphin" }, + new RaceAnimal { Icon = "🐞", Name = "Ladybird" }, + new RaceAnimal { Icon = "🦀", Name = "Crab" }, + new RaceAnimal { Icon = "🦄", Name = "Unicorn" } + }); + bc.EightBallResponses.AddRange(new HashSet + { + new EightBallResponse() { Text = "Most definitely yes" }, + new EightBallResponse() { Text = "For sure" }, + new EightBallResponse() { Text = "Totally!" }, + new EightBallResponse() { Text = "Of course!" }, + new EightBallResponse() { Text = "As I see it, yes" }, + new EightBallResponse() { Text = "My sources say yes" }, + new EightBallResponse() { Text = "Yes" }, + new EightBallResponse() { Text = "Most likely" }, + new EightBallResponse() { Text = "Perhaps" }, + new EightBallResponse() { Text = "Maybe" }, + new EightBallResponse() { Text = "Not sure" }, + new EightBallResponse() { Text = "It is uncertain" }, + new EightBallResponse() { Text = "Ask me again later" }, + new EightBallResponse() { Text = "Don't count on it" }, + new EightBallResponse() { Text = "Probably not" }, + new EightBallResponse() { Text = "Very doubtful" }, + new EightBallResponse() { Text = "Most likely no" }, + new EightBallResponse() { Text = "Nope" }, + new EightBallResponse() { Text = "No" }, + new EightBallResponse() { Text = "My sources say no" }, + new EightBallResponse() { Text = "Dont even think about it" }, + new EightBallResponse() { Text = "Definitely no" }, + new EightBallResponse() { Text = "NO - It may cause disease contraction" } + }); + + BotConfig.Add(bc); + + this.SaveChanges(); + } + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + #region QUOTES + + var quoteEntity = modelBuilder.Entity(); + + #endregion + + #region Donators + + var donatorEntity = modelBuilder.Entity(); + donatorEntity + .HasIndex(d => d.UserId) + .IsUnique(); + + #endregion + + #region GuildConfig + + var configEntity = modelBuilder.Entity(); + configEntity + .HasIndex(c => c.GuildId) + .IsUnique(); + + #endregion + + #region BotConfig + var botConfigEntity = modelBuilder.Entity(); + //botConfigEntity + // .HasMany(c => c.ModulePrefixes) + // .WithOne(mp => mp.BotConfig) + // .HasForeignKey(mp => mp.BotConfigId); + + #endregion + + #region ClashOfClans + + var callersEntity = modelBuilder.Entity(); + callersEntity + .HasOne(c => c.ClashWar) + .WithMany(c => c.Bases); + + #endregion + + #region Self Assignable Roles + + var selfassignableRolesEntity = modelBuilder.Entity(); + + selfassignableRolesEntity + .HasIndex(s => new { s.GuildId, s.RoleId }) + .IsUnique(); + + #endregion + + #region Repeater + + var repeaterEntity = modelBuilder.Entity(); + + repeaterEntity + .HasIndex(r => r.ChannelId) + .IsUnique(); + + #endregion + + #region Currency + var currencyEntity = modelBuilder.Entity(); + + currencyEntity + .HasIndex(c => c.UserId) + .IsUnique(); + #endregion + + #region Permission + var permissionEntity = modelBuilder.Entity(); + permissionEntity + .HasOne(p => p.Next) + .WithOne(p => p.Previous) + .IsRequired(false); + #endregion + + #region LogSettings + + //var logSettingEntity = modelBuilder.Entity(); + + //logSettingEntity + // .HasMany(ls => ls.IgnoredChannels) + // .WithOne(ls => ls.LogSetting) + // .HasPrincipalKey(ls => ls.id; + + //logSettingEntity + // .HasMany(ls => ls.IgnoredVoicePresenceChannelIds) + // .WithOne(ls => ls.LogSetting); + #endregion + + #region MusicPlaylists + var musicPlaylistEntity = modelBuilder.Entity(); + + musicPlaylistEntity + .HasMany(p => p.Songs) + .WithOne() + .OnDelete(Microsoft.EntityFrameworkCore.Metadata.DeleteBehavior.Cascade); + + + #endregion + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IBotConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/IBotConfigRepository.cs new file mode 100644 index 00000000..7f9f3dd5 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IBotConfigRepository.cs @@ -0,0 +1,9 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IBotConfigRepository : IRepository + { + BotConfig GetOrCreate(); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs b/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs new file mode 100644 index 00000000..756e9789 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IClashOfClansRepository.cs @@ -0,0 +1,10 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IClashOfClansRepository : IRepository + { + IEnumerable GetAllWars(); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/ICurrencyRepository.cs b/src/NadekoBot/Services/Database/Repositories/ICurrencyRepository.cs new file mode 100644 index 00000000..1658a16a --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/ICurrencyRepository.cs @@ -0,0 +1,13 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface ICurrencyRepository : IRepository + { + Currency GetOrCreate(ulong userId); + long GetUserCurrency(ulong userId); + bool TryUpdateState(ulong userId, long change); + IEnumerable GetTopRichest(int count); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/ICurrencyTransactionsRepository.cs b/src/NadekoBot/Services/Database/Repositories/ICurrencyTransactionsRepository.cs new file mode 100644 index 00000000..dd583ed9 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/ICurrencyTransactionsRepository.cs @@ -0,0 +1,8 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface ICurrencyTransactionsRepository : IRepository + { + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/ICustomReactionRepository.cs b/src/NadekoBot/Services/Database/Repositories/ICustomReactionRepository.cs new file mode 100644 index 00000000..83d10990 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/ICustomReactionRepository.cs @@ -0,0 +1,9 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface ICustomReactionRepository : IRepository + { + + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IDonatorsRepository.cs b/src/NadekoBot/Services/Database/Repositories/IDonatorsRepository.cs new file mode 100644 index 00000000..d0cfd699 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IDonatorsRepository.cs @@ -0,0 +1,11 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IDonatorsRepository : IRepository + { + IEnumerable GetDonatorsOrdered(); + Donator AddOrUpdateDonator(ulong userId, string name, int amount); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs new file mode 100644 index 00000000..ca3b651a --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IGuildConfigRepository.cs @@ -0,0 +1,15 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IGuildConfigRepository : IRepository + { + GuildConfig For(ulong guildId); + GuildConfig PermissionsFor(ulong guildId); + IEnumerable PermissionsForAll(); + GuildConfig SetNewRootPermission(ulong guildId, Permission p); + IEnumerable GetAllFollowedStreams(); + void SetCleverbotEnabled(ulong id, bool cleverbotEnabled); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IMusicPlaylistRepository.cs b/src/NadekoBot/Services/Database/Repositories/IMusicPlaylistRepository.cs new file mode 100644 index 00000000..d14b27c2 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IMusicPlaylistRepository.cs @@ -0,0 +1,11 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IMusicPlaylistRepository : IRepository + { + List GetPlaylistsOnPage(int num); + MusicPlaylist GetWithSongs(int id); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IQuoteRepository.cs b/src/NadekoBot/Services/Database/Repositories/IQuoteRepository.cs new file mode 100644 index 00000000..a3a92efd --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IQuoteRepository.cs @@ -0,0 +1,13 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IQuoteRepository : IRepository + { + IEnumerable GetAllQuotesByKeyword(ulong guildId, string keyword); + Task GetRandomQuoteByKeywordAsync(ulong guildId, string keyword); + IEnumerable GetGroup(int skip, int take); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs b/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs new file mode 100644 index 00000000..7c643ec7 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IReminderRepository.cs @@ -0,0 +1,9 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IReminderRepository : IRepository + { + + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IRepeaterRepository.cs b/src/NadekoBot/Services/Database/Repositories/IRepeaterRepository.cs new file mode 100644 index 00000000..2446c275 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IRepeaterRepository.cs @@ -0,0 +1,9 @@ +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IRepeaterRepository : IRepository + { + + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IRepository.cs b/src/NadekoBot/Services/Database/Repositories/IRepository.cs new file mode 100644 index 00000000..d66aae87 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IRepository.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IRepository where T : DbEntity + { + T Get(int id); + IEnumerable GetAll(); + + void Add(T obj); + void AddRange(params T[] objs); + + void Remove(int id); + void Remove(T obj); + void RemoveRange(params T[] objs); + + void Update(T obj); + void UpdateRange(params T[] objs); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/ISelfAssignedRolesRepository.cs b/src/NadekoBot/Services/Database/Repositories/ISelfAssignedRolesRepository.cs new file mode 100644 index 00000000..249ece0a --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/ISelfAssignedRolesRepository.cs @@ -0,0 +1,11 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface ISelfAssignedRolesRepository : IRepository + { + bool DeleteByGuildAndRoleId(ulong guildId, ulong roleId); + IEnumerable GetFromGuild(ulong guildId); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/IUnitConverterRepository.cs b/src/NadekoBot/Services/Database/Repositories/IUnitConverterRepository.cs new file mode 100644 index 00000000..527f0de1 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/IUnitConverterRepository.cs @@ -0,0 +1,11 @@ +using NadekoBot.Services.Database.Models; +using System; + +namespace NadekoBot.Services.Database.Repositories +{ + public interface IUnitConverterRepository : IRepository + { + void AddOrUpdate(Func check, ConvertUnit toAdd, Func toUpdate); + bool Empty(); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs new file mode 100644 index 00000000..e1ab3893 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/BotConfigRepository.cs @@ -0,0 +1,30 @@ +using NadekoBot.Services.Database.Models; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class BotConfigRepository : Repository, IBotConfigRepository + { + public BotConfigRepository(DbContext context) : base(context) + { + } + + public BotConfig GetOrCreate() + { + var config = _set.Include(bc => bc.RotatingStatusMessages) + .Include(bc => bc.RaceAnimals) + .Include(bc => bc.Blacklist) + .Include(bc => bc.EightBallResponses) + .Include(bc => bc.ModulePrefixes) + .FirstOrDefault(); + + if (config == null) + { + _set.Add(config = new BotConfig()); + _context.SaveChanges(); + } + return config; + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs new file mode 100644 index 00000000..54a391fa --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ClashOfClansRepository.cs @@ -0,0 +1,22 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class ClashOfClansRepository : Repository, IClashOfClansRepository + { + public ClashOfClansRepository(DbContext context) : base(context) + { + } + + public IEnumerable GetAllWars() + { + var toReturn = _set.Include(cw => cw.Bases) + .ToList(); + toReturn.ForEach(cw => cw.Bases = cw.Bases.Where(w => w.SequenceNumber != null).OrderBy(w => w.SequenceNumber).ToList()); + return toReturn; + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/CurrencyRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/CurrencyRepository.cs new file mode 100644 index 00000000..aac59172 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/CurrencyRepository.cs @@ -0,0 +1,57 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class CurrencyRepository : Repository, ICurrencyRepository + { + public CurrencyRepository(DbContext context) : base(context) + { + } + + public Currency GetOrCreate(ulong userId) + { + var cur = _set.FirstOrDefault(c => c.UserId == userId); + + if (cur == null) + { + _set.Add(cur = new Currency() + { + UserId = userId, + Amount = 0 + }); + _context.SaveChanges(); + } + return cur; + } + + public IEnumerable GetTopRichest(int count) => + _set.OrderByDescending(c => c.Amount).Take(count).ToList(); + + public long GetUserCurrency(ulong userId) => + GetOrCreate(userId).Amount; + + public bool TryUpdateState(ulong userId, long change) + { + var cur = GetOrCreate(userId); + + if (change == 0) + return true; + + if (change > 0) + { + cur.Amount += change; + return true; + } + //change is negative + if (cur.Amount + change >= 0) + { + cur.Amount += change; + return true; + } + return false; + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/CurrencyTransactionsRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/CurrencyTransactionsRepository.cs new file mode 100644 index 00000000..85a23a47 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/CurrencyTransactionsRepository.cs @@ -0,0 +1,12 @@ +using NadekoBot.Services.Database.Models; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class CurrencyTransactionsRepository : Repository, ICurrencyTransactionsRepository + { + public CurrencyTransactionsRepository(DbContext context) : base(context) + { + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/CustomReactionRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/CustomReactionRepository.cs new file mode 100644 index 00000000..f9b7c819 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/CustomReactionRepository.cs @@ -0,0 +1,12 @@ +using NadekoBot.Services.Database.Models; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class CustomReactionsRepository : Repository, ICustomReactionRepository + { + public CustomReactionsRepository(DbContext context) : base(context) + { + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/DonatorsRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/DonatorsRepository.cs new file mode 100644 index 00000000..b5c8c269 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/DonatorsRepository.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class DonatorsRepository : Repository, IDonatorsRepository + { + public DonatorsRepository(DbContext context) : base(context) + { + } + + public Donator AddOrUpdateDonator(ulong userId, string name, int amount) + { + var donator = _set.Where(d => d.UserId == userId).FirstOrDefault(); + + if (donator == null) + { + _set.Add(donator = new Donator + { + Amount = amount, + UserId = userId, + Name = name + }); + } + else + { + donator.Amount += amount; + donator.Name = name; + _set.Update(donator); + } + + return donator; + } + + public IEnumerable GetDonatorsOrdered() => + _set.OrderByDescending(d => d.Amount).ToList(); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs new file mode 100644 index 00000000..d90662ba --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/GuildConfigRepository.cs @@ -0,0 +1,132 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using NadekoBot.Modules.Permissions; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class GuildConfigRepository : Repository, IGuildConfigRepository + { + public GuildConfigRepository(DbContext context) : base(context) + { + } + + public new IEnumerable GetAll() => + _set.Include(gc => gc.LogSetting) + .ThenInclude(ls => ls.IgnoredChannels) + .Include(gc => gc.LogSetting) + .ThenInclude(ls => ls.IgnoredVoicePresenceChannelIds) + .Include(gc => gc.RootPermission) + .ThenInclude(gc => gc.Previous) + .Include(gc => gc.RootPermission) + .ThenInclude(gc => gc.Next) + .Include(gc => gc.GenerateCurrencyChannelIds) + .Include(gc => gc.FilterInvitesChannelIds) + .Include(gc => gc.FilterWordsChannelIds) + .Include(gc => gc.FilteredWords) + .Include(gc => gc.CommandCooldowns) + .ToList(); + + /// + /// Gets and creates if it doesn't exist a config for a guild. + /// + /// + /// + public GuildConfig For(ulong guildId) + { + var config = _set + .Include(gc => gc.FollowedStreams) + .Include(gc => gc.LogSetting) + .ThenInclude(ls => ls.IgnoredChannels) + .Include(gc => gc.LogSetting) + .ThenInclude(ls => ls.IgnoredVoicePresenceChannelIds) + .Include(gc => gc.FilterInvitesChannelIds) + .Include(gc => gc.FilterWordsChannelIds) + .Include(gc => gc.FilteredWords) + .Include(gc => gc.GenerateCurrencyChannelIds) + .Include(gc => gc.CommandCooldowns) + .FirstOrDefault(c => c.GuildId == guildId); + + if (config == null) + { + _set.Add((config = new GuildConfig + { + GuildId = guildId, + RootPermission = Permission.GetDefaultRoot(), + })); + _context.SaveChanges(); + } + return config; + } + + public GuildConfig PermissionsFor(ulong guildId) + { + var query = _set.Include(gc => gc.RootPermission); + + //todo this is possibly a disaster for performance + //What i could do instead is count the number of permissions in the permission table for this guild + // and make a for loop with those. + // or just select permissions for this guild and manually chain them + for (int i = 0; i < 60; i++) + { + query = query.ThenInclude(gc => gc.Next); + } + + var config = query.FirstOrDefault(c => c.GuildId == guildId); + + if (config == null) + { + _set.Add((config = new GuildConfig + { + GuildId = guildId, + RootPermission = Permission.GetDefaultRoot(), + })); + _context.SaveChanges(); + } + return config; + } + + public IEnumerable PermissionsForAll() + { + var query = _set.Include(gc => gc.RootPermission); + + //todo this is possibly a disaster for performance + //What i could do instead is count the number of permissions in the permission table for this guild + // and make a for loop with those. + // or just select permissions for this guild and manually chain them + for (int i = 0; i < 60; i++) + { + query = query.ThenInclude(gc => gc.Next); + } + + return query.ToList(); + } + + public IEnumerable GetAllFollowedStreams() => + _set.Include(gc => gc.FollowedStreams) + .SelectMany(gc => gc.FollowedStreams) + .ToList(); + + public GuildConfig SetNewRootPermission(ulong guildId, Permission p) + { + var data = _set + .Include(gc => gc.RootPermission) + .FirstOrDefault(gc => gc.GuildId == guildId); + + data.RootPermission.Prepend(p); + data.RootPermission = p; + return data; + } + + public void SetCleverbotEnabled(ulong id, bool cleverbotEnabled) + { + var conf = _set.FirstOrDefault(gc => gc.GuildId == id); + + if (conf == null) + return; + + conf.CleverbotEnabled = cleverbotEnabled; + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/MusicPlaylistRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/MusicPlaylistRepository.cs new file mode 100644 index 00000000..50347593 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/MusicPlaylistRepository.cs @@ -0,0 +1,30 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class MusicPlaylistRepository : Repository, IMusicPlaylistRepository + { + public MusicPlaylistRepository(DbContext context) : base(context) + { + } + + public List GetPlaylistsOnPage(int num) + { + if (num < 1) + throw new IndexOutOfRangeException(); + + return _set.Skip((num - 1) * 20) + .Take(20) + .Include(pl => pl.Songs) + .ToList(); + } + + public MusicPlaylist GetWithSongs(int id) => + _set.Include(mpl => mpl.Songs) + .FirstOrDefault(mpl => mpl.Id == id); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/QuoteRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/QuoteRepository.cs new file mode 100644 index 00000000..df8bddb6 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/QuoteRepository.cs @@ -0,0 +1,27 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class QuoteRepository : Repository, IQuoteRepository + { + public QuoteRepository(DbContext context) : base(context) + { + } + + public IEnumerable GetAllQuotesByKeyword(ulong guildId, string keyword) => + _set.Where(q => q.GuildId == guildId && q.Keyword == keyword); + + public IEnumerable GetGroup(int skip, int take) => + _set.OrderBy(q => q.Keyword).Skip(skip).Take(take).ToList(); + + public Task GetRandomQuoteByKeywordAsync(ulong guildId, string keyword) + { + var rng = new NadekoRandom(); + return _set.Where(q => q.GuildId == guildId && q.Keyword == keyword).OrderBy(q => rng.Next()).FirstOrDefaultAsync(); + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs new file mode 100644 index 00000000..fc7c28ff --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/ReminderRepository.cs @@ -0,0 +1,12 @@ +using NadekoBot.Services.Database.Models; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class ReminderRepository : Repository, IReminderRepository + { + public ReminderRepository(DbContext context) : base(context) + { + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/RepeaterRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/RepeaterRepository.cs new file mode 100644 index 00000000..94827f95 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/RepeaterRepository.cs @@ -0,0 +1,12 @@ +using NadekoBot.Services.Database.Models; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class RepeaterRepository : Repository, IRepeaterRepository + { + public RepeaterRepository(DbContext context) : base(context) + { + } + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/Repository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/Repository.cs new file mode 100644 index 00000000..b90cf154 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/Repository.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; +using System.Linq; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class Repository : IRepository where T : DbEntity + { + protected DbContext _context; + protected DbSet _set; + + public Repository(DbContext context) + { + _context = context; + _set = context.Set(); + } + + public void Add(T obj) => + _set.Add(obj); + + public void AddRange(params T[] objs) => + _set.AddRange(objs); + + public T Get(int id) => + _set.FirstOrDefault(e => e.Id == id); + + public IEnumerable GetAll() => + _set.ToList(); + + public void Remove(int id) => + _set.Remove(this.Get(id)); + + public void Remove(T obj) => + _set.Remove(obj); + + public void RemoveRange(params T[] objs) => + _set.RemoveRange(objs); + + public void Update(T obj) => + _set.Update(obj); + + public void UpdateRange(params T[] objs) => + _set.UpdateRange(objs); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/SelfAssignedRolesRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/SelfAssignedRolesRepository.cs new file mode 100644 index 00000000..77926648 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/SelfAssignedRolesRepository.cs @@ -0,0 +1,28 @@ +using NadekoBot.Services.Database.Models; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class SelfAssignedRolesRepository : Repository, ISelfAssignedRolesRepository + { + public SelfAssignedRolesRepository(DbContext context) : base(context) + { + } + + public bool DeleteByGuildAndRoleId(ulong guildId, ulong roleId) + { + var role = _set.Where(s => s.GuildId == guildId && s.RoleId == roleId).FirstOrDefault(); + + if (role == null) + return false; + + _set.Remove(role); + return true; + } + + public IEnumerable GetFromGuild(ulong guildId) => + _set.Where(s => s.GuildId == guildId).ToList(); + } +} diff --git a/src/NadekoBot/Services/Database/Repositories/Impl/UnitCOnverterRepository.cs b/src/NadekoBot/Services/Database/Repositories/Impl/UnitCOnverterRepository.cs new file mode 100644 index 00000000..2e3caa50 --- /dev/null +++ b/src/NadekoBot/Services/Database/Repositories/Impl/UnitCOnverterRepository.cs @@ -0,0 +1,26 @@ +using NadekoBot.Services.Database.Models; +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace NadekoBot.Services.Database.Repositories.Impl +{ + public class UnitConverterRepository : Repository, IUnitConverterRepository + { + public UnitConverterRepository(DbContext context) : base(context) + { + } + + public void AddOrUpdate(Func check, ConvertUnit toAdd, Func toUpdate) + { + var existing = _set.FirstOrDefault(check); + if (existing != null) + { + existing = toUpdate.Invoke(existing); + } + else _set.Add(toAdd); + } + + public bool Empty() => !_set.Any(); + } +} diff --git a/src/NadekoBot/Services/Database/UnitOfWork.cs b/src/NadekoBot/Services/Database/UnitOfWork.cs new file mode 100644 index 00000000..7ba343ee --- /dev/null +++ b/src/NadekoBot/Services/Database/UnitOfWork.cs @@ -0,0 +1,78 @@ +using NadekoBot.Services.Database.Repositories; +using NadekoBot.Services.Database.Repositories.Impl; +using System; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Database +{ + public class UnitOfWork : IUnitOfWork + { + public NadekoContext _context { get; } + + private IQuoteRepository _quotes; + public IQuoteRepository Quotes => _quotes ?? (_quotes = new QuoteRepository(_context)); + + private IGuildConfigRepository _guildConfigs; + public IGuildConfigRepository GuildConfigs => _guildConfigs ?? (_guildConfigs = new GuildConfigRepository(_context)); + + private IDonatorsRepository _donators; + public IDonatorsRepository Donators => _donators ?? (_donators = new DonatorsRepository(_context)); + + private IClashOfClansRepository _clashOfClans; + public IClashOfClansRepository ClashOfClans => _clashOfClans ?? (_clashOfClans = new ClashOfClansRepository(_context)); + + private IReminderRepository _reminders; + public IReminderRepository Reminders => _reminders ?? (_reminders = new ReminderRepository(_context)); + + private ISelfAssignedRolesRepository _selfAssignedRoles; + public ISelfAssignedRolesRepository SelfAssignedRoles => _selfAssignedRoles ?? (_selfAssignedRoles = new SelfAssignedRolesRepository(_context)); + + private IBotConfigRepository _botConfig; + public IBotConfigRepository BotConfig => _botConfig ?? (_botConfig = new BotConfigRepository(_context)); + + private IRepeaterRepository _repeaters; + public IRepeaterRepository Repeaters => _repeaters ?? (_repeaters = new RepeaterRepository(_context)); + + private ICurrencyRepository _currency; + public ICurrencyRepository Currency => _currency ?? (_currency = new CurrencyRepository(_context)); + + private ICurrencyTransactionsRepository _currencyTransactions; + public ICurrencyTransactionsRepository CurrencyTransactions => _currencyTransactions ?? (_currencyTransactions = new CurrencyTransactionsRepository(_context)); + + private IUnitConverterRepository _conUnits; + public IUnitConverterRepository ConverterUnits => _conUnits ?? (_conUnits = new UnitConverterRepository(_context)); + + private IMusicPlaylistRepository _musicPlaylists; + public IMusicPlaylistRepository MusicPlaylists => _musicPlaylists ?? (_musicPlaylists = new MusicPlaylistRepository(_context)); + + private ICustomReactionRepository _customReactions; + public ICustomReactionRepository CustomReactions => _customReactions ?? (_customReactions = new CustomReactionsRepository(_context)); + + public UnitOfWork(NadekoContext context) + { + _context = context; + } + + public int Complete() => + _context.SaveChanges(); + + public Task CompleteAsync() => + _context.SaveChangesAsync(); + + private bool disposed = false; + + protected void Dispose(bool disposing) + { + if (!this.disposed) + if (disposing) + _context.Dispose(); + this.disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/NadekoBot/Services/DbHandler.cs b/src/NadekoBot/Services/DbHandler.cs new file mode 100644 index 00000000..03d12a94 --- /dev/null +++ b/src/NadekoBot/Services/DbHandler.cs @@ -0,0 +1,44 @@ +using Microsoft.EntityFrameworkCore; +using NadekoBot.Services.Database; + +namespace NadekoBot.Services +{ + public class DbHandler + { + private static DbHandler _instance = null; + public static DbHandler Instance = _instance ?? (_instance = new DbHandler()); + private readonly DbContextOptions options; + + private string connectionString { get; } + + static DbHandler() { } + + private DbHandler() { + connectionString = NadekoBot.Credentials.Db.ConnectionString; + var optionsBuilder = new DbContextOptionsBuilder(); + optionsBuilder.UseSqlite(NadekoBot.Credentials.Db.ConnectionString); + options = optionsBuilder.Options; + //switch (NadekoBot.Credentials.Db.Type.ToUpperInvariant()) + //{ + // case "SQLITE": + // dbType = typeof(NadekoSqliteContext); + // break; + // //case "SQLSERVER": + // // dbType = typeof(NadekoSqlServerContext); + // // break; + // default: + // break; + + //} + } + + public NadekoContext GetDbContext() => + new NadekoContext(options); + + public IUnitOfWork GetUnitOfWork() => + new UnitOfWork(GetDbContext()); + + public static IUnitOfWork UnitOfWork() => + DbHandler.Instance.GetUnitOfWork(); + } +} diff --git a/src/NadekoBot/Services/IBotCredentials.cs b/src/NadekoBot/Services/IBotCredentials.cs new file mode 100644 index 00000000..88bc5986 --- /dev/null +++ b/src/NadekoBot/Services/IBotCredentials.cs @@ -0,0 +1,31 @@ +using Discord; + +namespace NadekoBot.Services +{ + public interface IBotCredentials + { + ulong ClientId { get; } + ulong BotId { get; } + + string Token { get; } + string GoogleApiKey { get; } + ulong[] OwnerIds { get; } + string MashapeKey { get; } + string LoLApiKey { get; } + + DBConfig Db { get; } + + bool IsOwner(IUser u); + } + + public class DBConfig + { + public DBConfig(string type, string connString) + { + this.Type = type; + this.ConnectionString = connString; + } + public string Type { get; } + public string ConnectionString { get; } + } +} diff --git a/src/NadekoBot/Services/IGoogleApiService.cs b/src/NadekoBot/Services/IGoogleApiService.cs new file mode 100644 index 00000000..b79bdb64 --- /dev/null +++ b/src/NadekoBot/Services/IGoogleApiService.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace NadekoBot.Services +{ + public interface IGoogleApiService + { + Task> GetVideosByKeywordsAsync(string keywords, int count = 1); + Task> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1); + Task> GetRelatedVideosAsync(string url, int count = 1); + Task> GetPlaylistTracksAsync(string playlistId, int count = 50); + Task> GetVideoDurationsAsync(IEnumerable videoIds); + + Task ShortenUrl(string url); + } +} diff --git a/src/NadekoBot/Services/ILocalization.cs b/src/NadekoBot/Services/ILocalization.cs new file mode 100644 index 00000000..892278da --- /dev/null +++ b/src/NadekoBot/Services/ILocalization.cs @@ -0,0 +1,7 @@ +namespace NadekoBot.Services +{ + public interface ILocalization + { + string this[string key] { get; } + } +} diff --git a/src/NadekoBot/Services/IStatsService.cs b/src/NadekoBot/Services/IStatsService.cs new file mode 100644 index 00000000..b1cb948f --- /dev/null +++ b/src/NadekoBot/Services/IStatsService.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; + +namespace NadekoBot.Services +{ + public interface IStatsService + { + Task Print(); + Task Reset(); + } +} diff --git a/src/NadekoBot/Services/Impl/BotCredentials.cs b/src/NadekoBot/Services/Impl/BotCredentials.cs new file mode 100644 index 00000000..fdd0a1fa --- /dev/null +++ b/src/NadekoBot/Services/Impl/BotCredentials.cs @@ -0,0 +1,110 @@ +using Newtonsoft.Json; +using System; +using System.IO; +using Discord; +using System.Linq; +using NLog; +using Microsoft.Extensions.Configuration; + +namespace NadekoBot.Services.Impl +{ + public class BotCredentials : IBotCredentials + { + private Logger _log; + + public ulong ClientId { get; } + public ulong BotId { get; } + + public string GoogleApiKey { get; } + + public string MashapeKey { get; } + + public string Token { get; } + + public ulong[] OwnerIds { get; } + + public string LoLApiKey { get; } + public string OsuApiKey { get; } + public string SoundCloudClientId { get; } + + public DBConfig Db { get; } + public int TotalShards { get; } + public string CarbonKey { get; } + + public string credsFileName { get; } = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json"); + + public BotCredentials() + { + _log = LogManager.GetCurrentClassLogger(); + + try { File.WriteAllText("./credentials_example.json", JsonConvert.SerializeObject(new CredentialsModel(), Formatting.Indented)); } catch { } + if(!File.Exists(credsFileName)) + _log.Warn($"credentials.json is missing. Attempting to load creds from environment variables prefixed with 'NadekoBot_'. Example is in {Path.GetFullPath("./credentials_example.json")}"); + try + { + var configBuilder = new ConfigurationBuilder(); + configBuilder.AddJsonFile(credsFileName, true) + .AddEnvironmentVariables("NadekoBot_"); + + var data = configBuilder.Build(); + + Token = data[nameof(Token)]; + if (string.IsNullOrWhiteSpace(Token)) + throw new ArgumentNullException(nameof(Token), "Token is missing from credentials.json or Environment varibles."); + OwnerIds = data.GetSection("OwnerIds").GetChildren().Select(c => ulong.Parse(c.Value)).ToArray(); + LoLApiKey = data[nameof(LoLApiKey)]; + GoogleApiKey = data[nameof(GoogleApiKey)]; + MashapeKey = data[nameof(MashapeKey)]; + OsuApiKey = data[nameof(OsuApiKey)]; + + int ts = 1; + int.TryParse(data[nameof(TotalShards)], out ts); + TotalShards = ts < 1 ? 1 : ts; + + ulong clId = 0; + ulong.TryParse(data[nameof(ClientId)], out clId); + ClientId = clId; + + SoundCloudClientId = data[nameof(SoundCloudClientId)]; + CarbonKey = data[nameof(CarbonKey)]; + var dbSection = data.GetSection("db"); + Db = new DBConfig(string.IsNullOrWhiteSpace(dbSection["Type"]) + ? "sqlite" + : dbSection["Type"], + string.IsNullOrWhiteSpace(dbSection["ConnectionString"]) + ? "Filename=./data/NadekoBot.db" + : dbSection["ConnectionString"]); + } + catch (Exception ex) + { + _log.Fatal(ex.Message); + _log.Fatal(ex); + throw; + } + + } + + private class CredentialsModel + { + public ulong ClientId { get; set; } = 123123123; + public string Token { get; set; } = ""; + public ulong[] OwnerIds { get; set; } = new ulong[1]; + public string LoLApiKey { get; set; } = ""; + public string GoogleApiKey { get; set; } = ""; + public string MashapeKey { get; set; } = ""; + public string OsuApiKey { get; set; } = ""; + public string SoundCloudClientId { get; set; } = ""; + public string CarbonKey { get; set; } = ""; + public DBConfig Db { get; set; } = new DBConfig("sqlite", "Filename=./data/NadekoBot.db"); + public int TotalShards { get; set; } = 1; + } + + private class DbModel + { + public string Type { get; set; } + public string ConnectionString { get; set; } + } + + public bool IsOwner(IUser u) => OwnerIds.Contains(u.Id); + } +} diff --git a/src/NadekoBot/Services/Impl/GoogleApiService.cs b/src/NadekoBot/Services/Impl/GoogleApiService.cs new file mode 100644 index 00000000..cb401476 --- /dev/null +++ b/src/NadekoBot/Services/Impl/GoogleApiService.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Google.Apis.YouTube.v3; +using Google.Apis.Services; +using System.Text.RegularExpressions; +using Google.Apis.Urlshortener.v1; +using Google.Apis.Urlshortener.v1.Data; +using NLog; + +namespace NadekoBot.Services.Impl +{ + public class GoogleApiService : IGoogleApiService + { + private YouTubeService yt; + private UrlshortenerService sh; + private Logger _log { get; } + + public GoogleApiService() + { + var bcs = new BaseClientService.Initializer + { + ApplicationName = "Nadeko Bot", + ApiKey = NadekoBot.Credentials.GoogleApiKey + }; + + _log = LogManager.GetCurrentClassLogger(); + + yt = new YouTubeService(bcs); + sh = new UrlshortenerService(bcs); + } + public async Task> GetPlaylistIdsByKeywordsAsync(string keywords, int count = 1) + { + if (string.IsNullOrWhiteSpace(keywords)) + throw new ArgumentNullException(nameof(keywords)); + + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); + + var match = new Regex("(?:youtu\\.be\\/|list=)(?[\\da-zA-Z\\-_]*)").Match(keywords); + if (match.Length > 1) + { + return new[] { match.Groups["id"].Value.ToString() }; + } + var query = yt.Search.List("snippet"); + query.MaxResults = count; + query.Type = "playlist"; + query.Q = keywords; + + return (await query.ExecuteAsync()).Items.Select(i => i.Id.PlaylistId); + } + + public async Task> GetRelatedVideosAsync(string id, int count = 1) + { + if (string.IsNullOrWhiteSpace(id)) + throw new ArgumentNullException(nameof(id)); + + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); + + var match = new Regex("(?:youtu\\.be\\/|v=)(?[\\da-zA-Z\\-_]*)").Match(id); + if (match.Length > 1) + { + id = match.Groups["id"].Value; + } + var query = yt.Search.List("snippet"); + query.MaxResults = count; + query.RelatedToVideoId = id; + query.Type = "video"; + return (await query.ExecuteAsync()).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId); + } + + public async Task> GetVideosByKeywordsAsync(string keywords, int count = 1) + { + if (string.IsNullOrWhiteSpace(keywords)) + throw new ArgumentNullException(nameof(keywords)); + + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); + + var query = yt.Search.List("snippet"); + query.MaxResults = count; + query.Q = keywords; + query.Type = "video"; + return (await query.ExecuteAsync()).Items.Select(i => "http://www.youtube.com/watch?v=" + i.Id.VideoId); + } + + public async Task ShortenUrl(string url) + { + if (string.IsNullOrWhiteSpace(url)) + throw new ArgumentNullException(nameof(url)); + + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.GoogleApiKey)) + return url; + + try + { + var response = await sh.Url.Insert(new Url { LongUrl = url }).ExecuteAsync(); + return response.Id; + } + catch (Exception ex) + { + _log.Warn(ex); + return url; + } + } + + public async Task> GetPlaylistTracksAsync(string playlistId, int count = 50) + { + if (string.IsNullOrWhiteSpace(playlistId)) + throw new ArgumentNullException(nameof(playlistId)); + + if (count <= 0) + throw new ArgumentOutOfRangeException(nameof(count)); + + string nextPageToken = null; + + List toReturn = new List(count); + + do + { + var toGet = count > 50 ? 50 : count; + count -= toGet; + + var query = yt.PlaylistItems.List("contentDetails"); + query.MaxResults = toGet; + query.PlaylistId = playlistId; + query.PageToken = nextPageToken; + + var data = await query.ExecuteAsync(); + + toReturn.AddRange(data.Items.Select(i => i.ContentDetails.VideoId)); + nextPageToken = data.NextPageToken; + } + while (count > 0 && !string.IsNullOrWhiteSpace(nextPageToken)); + + return toReturn; + } + + public async Task> GetVideoDurationsAsync(IEnumerable videoIds) + { + var videoIdsList = videoIds as List ?? videoIds.ToList(); + + Dictionary toReturn = new Dictionary(); + + if (!videoIdsList.Any()) + return toReturn; + var toGet = 0; + var remaining = videoIdsList.Count; + + do + { + toGet = remaining > 50 ? 50 : remaining; + remaining -= toGet; + + var q = yt.Videos.List("contentDetails"); + q.Id = string.Join(",", videoIds); + var items = (await q.ExecuteAsync().ConfigureAwait(false)).Items; + foreach (var i in items) + { + toReturn.Add(i.Id, System.Xml.XmlConvert.ToTimeSpan(i.ContentDetails.Duration)); + } + } + while (remaining > 0); + + return toReturn; + } + } +} diff --git a/src/NadekoBot/Services/Impl/Localization.cs b/src/NadekoBot/Services/Impl/Localization.cs new file mode 100644 index 00000000..0bcecd45 --- /dev/null +++ b/src/NadekoBot/Services/Impl/Localization.cs @@ -0,0 +1,45 @@ +namespace NadekoBot.Services +{ + public class Localization : ILocalization + { + public string this[string key] => LoadCommandString(key); + + public static string LoadCommandString(string key) + { + string toReturn = Resources.CommandStrings.ResourceManager.GetString(key); + return string.IsNullOrWhiteSpace(toReturn) ? key : toReturn; + } + + //private static string GetCommandString(string key) + //{ + // return key; + //var resx = new List(); + //var fs = new StreamReader(File.OpenRead("./Strings.resx")); + //Console.WriteLine(fs.ReadToEnd()); + //using (var reader = new ResourceReader(fs.BaseStream)) + //{ + // List existing = new List(); + // foreach (DictionaryEntry item in reader) + // { + // existing.Add(item); + // } + // var existingResource = resx.Where(r => r.Key.ToString() == key).FirstOrDefault(); + // if (existingResource.Key == null) + // { + // resx.Add(new DictionaryEntry() { Key = key, Value = key }); + // } + // else + // return existingResource.Value.ToString(); + //} + //using (var writer = new ResourceWriter(new FileStream("./Strings.resx", FileMode.OpenOrCreate))) + //{ + // resx.ForEach(r => + // { + // writer.AddResource(r.Key.ToString(), r.Value.ToString()); + // }); + // writer.Generate(); + //} + //return key; + //} + } +} diff --git a/src/NadekoBot/Services/Impl/StatsService.cs b/src/NadekoBot/Services/Impl/StatsService.cs new file mode 100644 index 00000000..e0916306 --- /dev/null +++ b/src/NadekoBot/Services/Impl/StatsService.cs @@ -0,0 +1,98 @@ +using Discord; +using Discord.WebSocket; +using NadekoBot.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace NadekoBot.Services.Impl +{ + public class StatsService : IStatsService + { + private ShardedDiscordClient client; + private DateTime started; + + public const string BotVersion = "1.0-rc2"; + + public string Author => "Kwoth#2560"; + public string Library => "Discord.Net"; + public int MessageCounter { get; private set; } = 0; + public int CommandsRan { get; private set; } = 0; + public string Heap => Math.Round((double)GC.GetTotalMemory(false) / 1.MiB(), 2).ToString(); + public double MessagesPerSecond => MessageCounter / (double)GetUptime().TotalSeconds; + public int TextChannels => client.GetGuilds().SelectMany(g => g.GetChannels().Where(c => c is ITextChannel)).Count(); + public int VoiceChannels => client.GetGuilds().SelectMany(g => g.GetChannels().Where(c => c is IVoiceChannel)).Count(); + public string OwnerIds => string.Join(", ", NadekoBot.Credentials.OwnerIds); + + + + Timer carbonitexTimer { get; } + + public StatsService(ShardedDiscordClient client, CommandHandler cmdHandler) + { + + this.client = client; + + Reset(); + this.client.MessageReceived += _ => Task.FromResult(MessageCounter++); + cmdHandler.CommandExecuted += (_, e) => CommandsRan++; + + this.client.Disconnected += _ => Reset(); + + this.carbonitexTimer = new Timer(async (state) => + { + if (string.IsNullOrWhiteSpace(NadekoBot.Credentials.CarbonKey)) + return; + try + { + using (var http = new HttpClient()) + { + using (var content = new FormUrlEncodedContent( + new Dictionary { + { "servercount", this.client.GetGuilds().Count.ToString() }, + { "key", NadekoBot.Credentials.CarbonKey }})) + { + content.Headers.Clear(); + content.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + + var res = await http.PostAsync("https://www.carbonitex.net/discord/data/botdata.php", content).ConfigureAwait(false); + } + }; + } + catch { } + }, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); + } + public async Task Print() + { + var curUser = await client.GetCurrentUserAsync(); + return $@" +Author: [{Author}] | Library: [{Library}] +Bot Version: [{BotVersion}] +Bot ID: {curUser.Id} +Owner ID(s): {OwnerIds} +Uptime: {GetUptimeString()} +Servers: {client.GetGuilds().Count} | TextChannels: {TextChannels} | VoiceChannels: {VoiceChannels} +Commands Ran this session: {CommandsRan} +Messages: {MessageCounter} [{MessagesPerSecond:F2}/sec] Heap: [{Heap} MB]"; + } + + public Task Reset() + { + MessageCounter = 0; + started = DateTime.Now; + return Task.CompletedTask; + } + + public TimeSpan GetUptime() => + DateTime.Now - started; + + public string GetUptimeString(string separator = ", ") + { + var time = GetUptime(); + return $"{time.Days} days{separator}{time.Hours} hours{separator}{time.Minutes} minutes"; + } + } +} diff --git a/src/NadekoBot/Services/NadekoRandom.cs b/src/NadekoBot/Services/NadekoRandom.cs new file mode 100644 index 00000000..0d42c065 --- /dev/null +++ b/src/NadekoBot/Services/NadekoRandom.cs @@ -0,0 +1,68 @@ +using System; +using System.Security.Cryptography; + +namespace NadekoBot.Services +{ + public class NadekoRandom : Random + { + RandomNumberGenerator rng; + + public NadekoRandom() : base() + { + rng = RandomNumberGenerator.Create(); + } + + private NadekoRandom(int Seed) : base(Seed) + { + rng = RandomNumberGenerator.Create(); + } + + public override int Next() + { + var bytes = new byte[sizeof(int)]; + rng.GetBytes(bytes); + return Math.Abs(BitConverter.ToInt32(bytes, 0)); + } + + public override int Next(int maxValue) + { + if (maxValue <= 0) + throw new ArgumentOutOfRangeException(); + var bytes = new byte[sizeof(int)]; + rng.GetBytes(bytes); + return Math.Abs(BitConverter.ToInt32(bytes, 0)) % maxValue; + } + + public override int Next(int minValue, int maxValue) + { + if (minValue > maxValue) + throw new ArgumentOutOfRangeException(); + if (minValue == maxValue) + return minValue; + var bytes = new byte[sizeof(int)]; + rng.GetBytes(bytes); + var num = BitConverter.ToInt32(bytes, 0); + var sign = Math.Sign(BitConverter.ToInt32(bytes, 0)); + return (sign * BitConverter.ToInt32(bytes, 0)) % (maxValue - minValue) + minValue; + } + + public override void NextBytes(byte[] buffer) + { + rng.GetBytes(buffer); + } + + protected override double Sample() + { + var bytes = new byte[sizeof(double)]; + rng.GetBytes(bytes); + return Math.Abs(BitConverter.ToDouble(bytes, 0) / double.MaxValue + 1); + } + + public override double NextDouble() + { + var bytes = new byte[sizeof(double)]; + rng.GetBytes(bytes); + return BitConverter.ToDouble(bytes, 0); + } + } +} diff --git a/src/NadekoBot/ShardedDiscordClient.cs b/src/NadekoBot/ShardedDiscordClient.cs new file mode 100644 index 00000000..e7b43881 --- /dev/null +++ b/src/NadekoBot/ShardedDiscordClient.cs @@ -0,0 +1,103 @@ +using Discord; +using Discord.WebSocket; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NLog; + +namespace NadekoBot +{ + public class ShardedDiscordClient + { + private DiscordSocketConfig discordSocketConfig; + private Logger _log { get; } + + public Func UserJoined { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func MessageReceived { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func UserLeft { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func UserUpdated { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func, IMessage, Task> MessageUpdated { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func, Task> MessageDeleted { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func UserBanned { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func UserUnbanned { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func UserPresenceUpdated { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func UserVoiceStateUpdated { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func ChannelCreated { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func ChannelDestroyed { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func ChannelUpdated { get; internal set; } = delegate { return Task.CompletedTask; }; + public Func Disconnected { get; internal set; } = delegate { return Task.CompletedTask; }; + + private IReadOnlyList Clients { get; } + + public ShardedDiscordClient (DiscordSocketConfig discordSocketConfig) + { + _log = LogManager.GetCurrentClassLogger(); + this.discordSocketConfig = discordSocketConfig; + + var clientList = new List(); + for (int i = 0; i < discordSocketConfig.TotalShards; i++) + { + discordSocketConfig.ShardId = i; + var client = new DiscordSocketClient(discordSocketConfig); + clientList.Add(client); + client.UserJoined += async arg1 => await UserJoined(arg1); + client.MessageReceived += async arg1 => await MessageReceived(arg1); + client.UserLeft += async arg1 => await UserLeft(arg1); + client.UserUpdated += async (arg1, gu2) => await UserUpdated(arg1, gu2); + client.MessageUpdated += async (arg1, m2) => await MessageUpdated(arg1, m2); + client.MessageDeleted += async (arg1, arg2) => await MessageDeleted(arg1, arg2); + client.UserBanned += async (arg1, arg2) => await UserBanned(arg1, arg2); + client.UserPresenceUpdated += async (arg1, arg2, arg3) => await UserPresenceUpdated(arg1, arg2, arg3); + client.UserVoiceStateUpdated += async (arg1, arg2, arg3) => await UserVoiceStateUpdated(arg1, arg2, arg3); + client.ChannelCreated += async arg => await ChannelCreated(arg); + client.ChannelDestroyed += async arg => await ChannelDestroyed(arg); + client.ChannelUpdated += async (arg1, arg2) => await ChannelUpdated(arg1, arg2); + + _log.Info($"Shard #{i} initialized."); + } + + Clients = clientList.AsReadOnly(); + } + + public ISelfUser GetCurrentUser() => + Clients[0].GetCurrentUser(); + + public Task GetCurrentUserAsync() => + Clients[0].GetCurrentUserAsync(); + + public Task GetAllCurrentUsersAsync() => + Task.WhenAll(Clients.Select(c => c.GetCurrentUserAsync())); + + public IReadOnlyCollection GetGuilds() => + Clients.SelectMany(c => c.GetGuilds()).ToArray(); + + public IGuild GetGuild(ulong id) => + Clients.Select(c => c.GetGuild(id)).FirstOrDefault(g => g != null); + + public Task GetDMChannelAsync(ulong channelId) => + Clients[0].GetDMChannelAsync(channelId); + + internal Task LoginAsync(TokenType tokenType, string token) => + Task.WhenAll(Clients.Select(async c => { await c.LoginAsync(tokenType, token); _log.Info($"Shard #{c.ShardId} logged in."); })); + + internal Task ConnectAsync() => + Task.WhenAll(Clients.Select(async c => { await c.ConnectAsync(); _log.Info($"Shard #{c.ShardId} connected."); })); + + internal Task DownloadAllUsersAsync() => + Task.WhenAll(Clients.Select(async c => { await c.DownloadAllUsersAsync(); _log.Info($"Shard #{c.ShardId} downloaded {c.GetGuilds().Sum(g => g.GetUsers().Count)} users."); })); + + public async Task SetGame(string game) + { + await Task.WhenAll((await GetAllCurrentUsersAsync()) + .Select(u => u.ModifyStatusAsync(ms => ms.Game = new Discord.Game(game)))); + } + + public async Task SetStream(string name, string url) + { + await Task.WhenAll((await GetAllCurrentUsersAsync()) + .Select(u => u.ModifyStatusAsync(ms => ms.Game = new Discord.Game(name, url, StreamType.Twitch)))); + + } + } +} diff --git a/src/NadekoBot/TypeReaders/BotCommandTypeReader.cs b/src/NadekoBot/TypeReaders/BotCommandTypeReader.cs new file mode 100644 index 00000000..91763e8a --- /dev/null +++ b/src/NadekoBot/TypeReaders/BotCommandTypeReader.cs @@ -0,0 +1,22 @@ +using Discord.Commands; +using System.Linq; +using System.Threading.Tasks; +using Discord; + +namespace NadekoBot.TypeReaders +{ + public class CommandTypeReader : TypeReader + { + public override Task Read(IUserMessage context, string input) + { + input = input.ToUpperInvariant(); + var cmd = NadekoBot.CommandService.Commands.FirstOrDefault(c => + c.Aliases.Select(a => a.ToUpperInvariant()).Contains(input) || + c.Text.ToUpperInvariant() == input); + if (cmd == null) + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such command found.")); + + return Task.FromResult(TypeReaderResult.FromSuccess(cmd)); + } + } +} diff --git a/src/NadekoBot/TypeReaders/GuildTypeReader.cs b/src/NadekoBot/TypeReaders/GuildTypeReader.cs new file mode 100644 index 00000000..ab9a5259 --- /dev/null +++ b/src/NadekoBot/TypeReaders/GuildTypeReader.cs @@ -0,0 +1,23 @@ +using Discord.Commands; +using System.Linq; +using System.Threading.Tasks; +using Discord; + +namespace NadekoBot.TypeReaders +{ + public class GuildTypeReader : TypeReader + { + public override Task Read(IUserMessage context, string input) + { + input = input.Trim().ToLowerInvariant(); + var guilds = NadekoBot.Client.GetGuilds(); + var guild = guilds.FirstOrDefault(g => g.Id.ToString().Trim().ToLowerInvariant() == input) ?? //by id + guilds.FirstOrDefault(g => g.Name.Trim().ToLowerInvariant() == input); //by name + + if (guild != null) + return Task.FromResult(TypeReaderResult.FromSuccess(guild)); + + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No guild by that name or Id found")); + } + } +} diff --git a/src/NadekoBot/TypeReaders/ModuleTypeReader.cs b/src/NadekoBot/TypeReaders/ModuleTypeReader.cs new file mode 100644 index 00000000..366429ec --- /dev/null +++ b/src/NadekoBot/TypeReaders/ModuleTypeReader.cs @@ -0,0 +1,20 @@ +using Discord.Commands; +using System.Linq; +using System.Threading.Tasks; +using Discord; + +namespace NadekoBot.TypeReaders +{ + public class ModuleTypeReader : TypeReader + { + public override Task Read(IUserMessage context, string input) + { + input = input.ToUpperInvariant(); + var module = NadekoBot.CommandService.Modules.FirstOrDefault(m => m.Name.ToUpperInvariant() == input); + if (module == null) + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "No such module found.")); + + return Task.FromResult(TypeReaderResult.FromSuccess(module)); + } + } +} diff --git a/src/NadekoBot/TypeReaders/PermissionActionTypeReader.cs b/src/NadekoBot/TypeReaders/PermissionActionTypeReader.cs new file mode 100644 index 00000000..209f69b1 --- /dev/null +++ b/src/NadekoBot/TypeReaders/PermissionActionTypeReader.cs @@ -0,0 +1,41 @@ +using Discord.Commands; +using System.Threading.Tasks; +using Discord; +using NadekoBot.Modules.Permissions; + +namespace NadekoBot.TypeReaders +{ + /// + /// Used instead of bool for more flexible keywords for true/false only in the permission module + /// + public class PermissionActionTypeReader : TypeReader + { + public override Task Read(IUserMessage context, string input) + { + input = input.ToUpperInvariant(); + switch (input) + { + case "1": + case "T": + case "TRUE": + case "ENABLE": + case "ENABLED": + case "ALLOW": + case "PERMIT": + case "UNBAN": + return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Enable)); + case "0": + case "F": + case "FALSE": + case "DENY": + case "DISABLE": + case "DISABLED": + case "DISALLOW": + case "BAN": + return Task.FromResult(TypeReaderResult.FromSuccess(PermissionAction.Disable)); + default: + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Did not receive a valid boolean value")); + } + } + } +} diff --git a/src/NadekoBot/_Extensions/Extensions.cs b/src/NadekoBot/_Extensions/Extensions.cs new file mode 100644 index 00000000..0859d080 --- /dev/null +++ b/src/NadekoBot/_Extensions/Extensions.cs @@ -0,0 +1,368 @@ +using Discord; +using Discord.API; +using Discord.WebSocket; +using ImageSharp; +using Newtonsoft.Json; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace NadekoBot.Extensions +{ + public static class Extensions + { + public static void AddFakeHeaders(this HttpClient http) + { + http.DefaultRequestHeaders.Clear(); + http.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1"); + http.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + } + + public static async Task SendMessageToOwnerAsync(this IGuild guild, string message) + { + var ownerPrivate = await (await guild.GetOwnerAsync().ConfigureAwait(false)).CreateDMChannelAsync() + .ConfigureAwait(false); + + return await ownerPrivate.SendMessageAsync(message).ConfigureAwait(false); + } + + public static IEnumerable ForEach(this IEnumerable elems, Action exec) + { + foreach (var elem in elems) + { + exec(elem); + } + return elems; + } + + public static void AddRange(this HashSet target, IEnumerable elements) where T : class + { + foreach (var item in elements) + { + target.Add(item); + } + } + + public static void AddRange(this ConcurrentHashSet target, IEnumerable elements) where T : class + { + foreach (var item in elements) + { + target.Add(item); + } + } + + public static bool IsInteger(this decimal number) => number == Math.Truncate(number); + + public static string SanitizeMentions(this string str) => + str.Replace("@everyone", "@everyοne").Replace("@here", "@һere"); + + public static double UnixTimestamp(this DateTime dt) => dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1)).TotalSeconds; + + public static async Task SendMessageAsync(this IGuildUser user, string message, bool isTTS = false) => + await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(message, isTTS).ConfigureAwait(false); + + public static async Task SendFileAsync(this IGuildUser user, string filePath, string caption = null, bool isTTS = false) => + await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, caption, isTTS).ConfigureAwait(false); + + public static async Task SendFileAsync(this IGuildUser user, Stream fileStream, string fileName, string caption = null, bool isTTS = false) => + await (await user.CreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(fileStream, fileName, caption, isTTS).ConfigureAwait(false); + + public static async Task Reply(this IUserMessage msg, string content) => + await msg.Channel.SendMessageAsync(content).ConfigureAwait(false); + + public static bool IsAuthor(this IUserMessage msg) => + NadekoBot.Client.GetCurrentUser().Id == msg.Author.Id; + + public static IEnumerable Members(this IRole role) => + NadekoBot.Client.GetGuild(role.GuildId)?.GetUsers().Where(u => u.Roles.Contains(role)) ?? Enumerable.Empty(); + + public static async Task ReplyLong(this IUserMessage msg, string content, string[] breakOn = null, string addToPartialEnd = "", string addToPartialStart = "") + { + if (content.Length == 0) return null; + var characterLimit = 1750; + if (content.Length < characterLimit) return new[] { await msg.Channel.SendMessageAsync(content).ConfigureAwait(false) }; + if (breakOn == null) breakOn = new[] { "\n", " ", " " }; + var list = new List(); + var splitItems = new List(); + foreach (var breaker in breakOn) + { + if (splitItems.Count == 0) + { + splitItems = Regex.Split(content, $"(?={breaker})").Where(s => !string.IsNullOrWhiteSpace(s)).ToList(); + } + else + { + for (int i = 0; i < splitItems.Count; i++) + { + var temp = splitItems[i]; + if (temp.Length > characterLimit) + { + var splitDeep = Regex.Split(temp, $"(?={breaker})").Where(s => !string.IsNullOrWhiteSpace(s)); + splitItems.RemoveAt(i); + splitItems.InsertRange(i, splitDeep); + } + } + } + if (splitItems.All(s => s.Length < characterLimit)) break; + } + //We remove any entries that are larger than 2000 chars + if (splitItems.Any(s => s.Length >= characterLimit)) + { + splitItems = splitItems.Where(s => s.Length < characterLimit).ToList(); + } + //ensured every item can be sent (if individually) + var firstItem = true; + Queue buildItems = new Queue(splitItems); + StringBuilder builder = new StringBuilder(); + + while (buildItems.Count > 0) + { + if (builder.Length == 0) + { + //first item to add + if (!firstItem) + builder.Append(addToPartialStart); + else + firstItem = false; + builder.Append(buildItems.Dequeue()); + } + else + { + builder.Append(buildItems.Dequeue()); + } + if (buildItems.Count == 0) + { + list.Add(await msg.Channel.SendMessageAsync(builder.ToString())); + builder.Clear(); + } + else + { + var peeked = buildItems.Peek(); + if (builder.Length + peeked.Length + addToPartialEnd.Length > characterLimit) + { + builder.Append(addToPartialEnd); + list.Add(await msg.Channel.SendMessageAsync(builder.ToString())); + builder.Clear(); + } + } + } + return list.ToArray(); + } + + public static Task EmbedAsync(this IMessageChannel ch, Discord.API.Embed embed) + => ch.SendMessageAsync("", embed: embed); + + public static Task SendErrorAsync(this IMessageChannel ch, string error, string title = null, string url = null) + => ch.SendMessageAsync("", embed: new Embed() { Description = error, Title = title, Url = url, Color = NadekoBot.ErrorColor }); + + public static Task SendTableAsync(this IMessageChannel ch, string seed, IEnumerable items, Func howToPrint, int columns = 3) + { + var i = 0; + return ch.SendMessageAsync($@"{seed}```css +{string.Join("\n", items.GroupBy(item => (i++) / columns) + .Select(ig => string.Concat(ig.Select(el => howToPrint(el)))))} +```"); + } + + public static Task SendTableAsync(this IMessageChannel ch, IEnumerable items, Func howToPrint, int columns = 3) + { + return ch.SendTableAsync("", items, howToPrint, columns); + } + + /// + /// returns an IEnumerable with randomized element order + /// + /// + /// + public static IEnumerable Shuffle(this IEnumerable items) + { + // Thanks to @Joe4Evr for finding a bug in the old version of the shuffle + using (var provider = RandomNumberGenerator.Create()) + { + var list = items.ToList(); + var n = list.Count; + while (n > 1) + { + var box = new byte[(n / Byte.MaxValue) + 1]; + int boxSum; + do + { + provider.GetBytes(box); + boxSum = box.Sum(b => b); + } + while (!(boxSum < n * ((Byte.MaxValue * box.Length) / n))); + var k = (boxSum % n); + n--; + var value = list[k]; + list[k] = list[n]; + list[n] = value; + } + return list; + } + } + + public static string TrimTo(this string str, int maxLength, bool hideDots = false) + { + if (maxLength < 0) + throw new ArgumentOutOfRangeException(nameof(maxLength), $"Argument {nameof(maxLength)} can't be negative."); + if (maxLength == 0) + return string.Empty; + if (maxLength <= 3) + return string.Concat(str.Select(c => '.')); + if (str.Length < maxLength) + return str; + return string.Concat(str.Take(maxLength - 3)) + (hideDots ? "" : "..."); + } + + public static string ToTitleCase(this string str) + { + var tokens = str.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); + for (var i = 0; i < tokens.Length; i++) + { + var token = tokens[i]; + tokens[i] = token.Substring(0, 1).ToUpper() + token.Substring(1); + } + + return string.Join(" ", tokens); + } + + /// + /// Removes trailing S or ES (if specified) on the given string if the num is 1 + /// + /// + /// + /// + /// String with the correct singular/plural form + public static string SnPl(this string str, int? num, bool es = false) + { + if (str == null) + throw new ArgumentNullException(nameof(str)); + if (num == null) + throw new ArgumentNullException(nameof(num)); + return num == 1 ? str.Remove(str.Length - 1, es ? 2 : 1) : str; + } + + //http://www.dotnetperls.com/levenshtein + public static int LevenshteinDistance(this string s, string t) + { + var n = s.Length; + var m = t.Length; + var d = new int[n + 1, m + 1]; + + // Step 1 + if (n == 0) + { + return m; + } + + if (m == 0) + { + return n; + } + + // Step 2 + for (var i = 0; i <= n; d[i, 0] = i++) + { + } + + for (var j = 0; j <= m; d[0, j] = j++) + { + } + + // Step 3 + for (var i = 1; i <= n; i++) + { + //Step 4 + for (var j = 1; j <= m; j++) + { + // Step 5 + var cost = (t[j - 1] == s[i - 1]) ? 0 : 1; + + // Step 6 + d[i, j] = Math.Min( + Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), + d[i - 1, j - 1] + cost); + } + } + // Step 7 + return d[n, m]; + } + + public static async Task ToStream(this string str) + { + var ms = new MemoryStream(); + var sw = new StreamWriter(ms); + await sw.WriteAsync(str); + await sw.FlushAsync(); + ms.Position = 0; + return ms; + + } + + public static string ToJson(this T any, Formatting formatting = Formatting.Indented) => + JsonConvert.SerializeObject(any, formatting); + + public static int KiB(this int value) => value * 1024; + public static int KB(this int value) => value * 1000; + + public static int MiB(this int value) => value.KiB() * 1024; + public static int MB(this int value) => value.KB() * 1000; + + public static int GiB(this int value) => value.MiB() * 1024; + 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 string Unmention(this string str) => str.Replace("@", "ම"); + + public static Image Merge(this IEnumerable images) + { + var imgList = images.ToList(); + + var canvas = new Image(imgList.Sum(img => img.Width), imgList.Max(img => img.Height)); + + var canvasPixels = canvas.Lock(); + int offsetX = 0; + foreach (var img in imgList.Select(img=>img.Lock())) + { + for (int i = 0; i < img.Width; i++) + { + for (int j = 0; j < img.Height; j++) + { + canvasPixels[i + offsetX, j] = img[i, j]; + } + } + offsetX += img.Width; + } + + return canvas; + } + + public static Stream ToStream(this Image img) + { + var imageStream = new MemoryStream(); + img.SaveAsPng(imageStream); + imageStream.Position = 0; + return imageStream; + } + + private static readonly Regex filterRegex = new Regex(@"(?:discord(?:\.gg|.me|app\.com\/invite)\/(?([\w]{16}|(?:[\w]+-?){3})))", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static bool IsDiscordInvite(this string str) + => filterRegex.IsMatch(str); + } +} diff --git a/src/NadekoBot/credentials.json b/src/NadekoBot/credentials.json new file mode 100644 index 00000000..31a9414f --- /dev/null +++ b/src/NadekoBot/credentials.json @@ -0,0 +1,15 @@ +{ + "ClientId": 123123123, + "BotId": null, + "Token": "", + "OwnerIds": [ + 0 + ], + "LoLApiKey": "", + "GoogleApiKey": "", + "MashapeKey": "", + "OsuApiKey": "", + "SoundCloudClientId": "", + "Db": null, + "TotalShards": 1 +} \ No newline at end of file diff --git a/src/NadekoBot/credentials_example.json b/src/NadekoBot/credentials_example.json new file mode 100644 index 00000000..240f9029 --- /dev/null +++ b/src/NadekoBot/credentials_example.json @@ -0,0 +1,18 @@ +{ + "ClientId": 123123123, + "Token": "", + "OwnerIds": [ + 0 + ], + "LoLApiKey": "", + "GoogleApiKey": "", + "MashapeKey": "", + "OsuApiKey": "", + "SoundCloudClientId": "", + "CarbonKey": "", + "Db": { + "Type": "sqlite", + "ConnectionString": "Filename=./data/NadekoBot.db" + }, + "TotalShards": 1 +} \ No newline at end of file diff --git a/NadekoBot/bin/Debug/data/currency_images/img1.jpg b/src/NadekoBot/data/currency_images/img1.jpg similarity index 100% rename from NadekoBot/bin/Debug/data/currency_images/img1.jpg rename to src/NadekoBot/data/currency_images/img1.jpg diff --git a/NadekoBot/bin/Debug/data/currency_images/img2.jpg b/src/NadekoBot/data/currency_images/img2.jpg similarity index 100% rename from NadekoBot/bin/Debug/data/currency_images/img2.jpg rename to src/NadekoBot/data/currency_images/img2.jpg diff --git a/NadekoBot/bin/Debug/data/currency_images/img3.jpg b/src/NadekoBot/data/currency_images/img3.jpg similarity index 100% rename from NadekoBot/bin/Debug/data/currency_images/img3.jpg rename to src/NadekoBot/data/currency_images/img3.jpg diff --git a/NadekoBot/resources/images/cards/ace_of_clubs.jpg b/src/NadekoBot/data/images/cards/ace_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/ace_of_clubs.jpg rename to src/NadekoBot/data/images/cards/ace_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/ace_of_diamonds.jpg b/src/NadekoBot/data/images/cards/ace_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/ace_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/ace_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/ace_of_hearts.jpg b/src/NadekoBot/data/images/cards/ace_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/ace_of_hearts.jpg rename to src/NadekoBot/data/images/cards/ace_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/ace_of_spades.jpg b/src/NadekoBot/data/images/cards/ace_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/ace_of_spades.jpg rename to src/NadekoBot/data/images/cards/ace_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/black_joker.jpg b/src/NadekoBot/data/images/cards/black_joker.jpg similarity index 100% rename from NadekoBot/resources/images/cards/black_joker.jpg rename to src/NadekoBot/data/images/cards/black_joker.jpg diff --git a/NadekoBot/resources/images/cards/8_of_clubs.jpg b/src/NadekoBot/data/images/cards/eight_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/8_of_clubs.jpg rename to src/NadekoBot/data/images/cards/eight_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/8_of_diamonds.jpg b/src/NadekoBot/data/images/cards/eight_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/8_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/eight_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/8_of_hearts.jpg b/src/NadekoBot/data/images/cards/eight_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/8_of_hearts.jpg rename to src/NadekoBot/data/images/cards/eight_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/8_of_spades.jpg b/src/NadekoBot/data/images/cards/eight_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/8_of_spades.jpg rename to src/NadekoBot/data/images/cards/eight_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/5_of_clubs.jpg b/src/NadekoBot/data/images/cards/five_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/5_of_clubs.jpg rename to src/NadekoBot/data/images/cards/five_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/5_of_diamonds.jpg b/src/NadekoBot/data/images/cards/five_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/5_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/five_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/5_of_hearts.jpg b/src/NadekoBot/data/images/cards/five_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/5_of_hearts.jpg rename to src/NadekoBot/data/images/cards/five_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/5_of_spades.jpg b/src/NadekoBot/data/images/cards/five_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/5_of_spades.jpg rename to src/NadekoBot/data/images/cards/five_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/4_of_clubs.jpg b/src/NadekoBot/data/images/cards/four_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/4_of_clubs.jpg rename to src/NadekoBot/data/images/cards/four_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/4_of_diamonds.jpg b/src/NadekoBot/data/images/cards/four_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/4_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/four_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/4_of_hearts.jpg b/src/NadekoBot/data/images/cards/four_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/4_of_hearts.jpg rename to src/NadekoBot/data/images/cards/four_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/4_of_spades.jpg b/src/NadekoBot/data/images/cards/four_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/4_of_spades.jpg rename to src/NadekoBot/data/images/cards/four_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/jack_of_clubs.jpg b/src/NadekoBot/data/images/cards/jack_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/jack_of_clubs.jpg rename to src/NadekoBot/data/images/cards/jack_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/jack_of_diamonds.jpg b/src/NadekoBot/data/images/cards/jack_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/jack_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/jack_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/jack_of_hearts.jpg b/src/NadekoBot/data/images/cards/jack_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/jack_of_hearts.jpg rename to src/NadekoBot/data/images/cards/jack_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/jack_of_spades.jpg b/src/NadekoBot/data/images/cards/jack_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/jack_of_spades.jpg rename to src/NadekoBot/data/images/cards/jack_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/king_of_clubs.jpg b/src/NadekoBot/data/images/cards/king_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/king_of_clubs.jpg rename to src/NadekoBot/data/images/cards/king_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/king_of_diamonds.jpg b/src/NadekoBot/data/images/cards/king_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/king_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/king_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/king_of_hearts.jpg b/src/NadekoBot/data/images/cards/king_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/king_of_hearts.jpg rename to src/NadekoBot/data/images/cards/king_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/king_of_spades.jpg b/src/NadekoBot/data/images/cards/king_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/king_of_spades.jpg rename to src/NadekoBot/data/images/cards/king_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/9_of_clubs.jpg b/src/NadekoBot/data/images/cards/nine_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/9_of_clubs.jpg rename to src/NadekoBot/data/images/cards/nine_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/9_of_diamonds.jpg b/src/NadekoBot/data/images/cards/nine_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/9_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/nine_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/9_of_hearts.jpg b/src/NadekoBot/data/images/cards/nine_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/9_of_hearts.jpg rename to src/NadekoBot/data/images/cards/nine_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/9_of_spades.jpg b/src/NadekoBot/data/images/cards/nine_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/9_of_spades.jpg rename to src/NadekoBot/data/images/cards/nine_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/queen_of_clubs.jpg b/src/NadekoBot/data/images/cards/queen_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/queen_of_clubs.jpg rename to src/NadekoBot/data/images/cards/queen_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/queen_of_diamonds.jpg b/src/NadekoBot/data/images/cards/queen_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/queen_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/queen_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/queen_of_hearts.jpg b/src/NadekoBot/data/images/cards/queen_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/queen_of_hearts.jpg rename to src/NadekoBot/data/images/cards/queen_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/queen_of_spades.jpg b/src/NadekoBot/data/images/cards/queen_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/queen_of_spades.jpg rename to src/NadekoBot/data/images/cards/queen_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/red_joker.jpg b/src/NadekoBot/data/images/cards/red_joker.jpg similarity index 100% rename from NadekoBot/resources/images/cards/red_joker.jpg rename to src/NadekoBot/data/images/cards/red_joker.jpg diff --git a/NadekoBot/resources/images/cards/7_of_clubs.jpg b/src/NadekoBot/data/images/cards/seven_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/7_of_clubs.jpg rename to src/NadekoBot/data/images/cards/seven_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/7_of_diamonds.jpg b/src/NadekoBot/data/images/cards/seven_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/7_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/seven_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/7_of_hearts.jpg b/src/NadekoBot/data/images/cards/seven_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/7_of_hearts.jpg rename to src/NadekoBot/data/images/cards/seven_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/7_of_spades.jpg b/src/NadekoBot/data/images/cards/seven_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/7_of_spades.jpg rename to src/NadekoBot/data/images/cards/seven_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/6_of_clubs.jpg b/src/NadekoBot/data/images/cards/six_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/6_of_clubs.jpg rename to src/NadekoBot/data/images/cards/six_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/6_of_diamonds.jpg b/src/NadekoBot/data/images/cards/six_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/6_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/six_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/6_of_hearts.jpg b/src/NadekoBot/data/images/cards/six_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/6_of_hearts.jpg rename to src/NadekoBot/data/images/cards/six_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/6_of_spades.jpg b/src/NadekoBot/data/images/cards/six_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/6_of_spades.jpg rename to src/NadekoBot/data/images/cards/six_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/10_of_clubs.jpg b/src/NadekoBot/data/images/cards/ten_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/10_of_clubs.jpg rename to src/NadekoBot/data/images/cards/ten_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/10_of_diamonds.jpg b/src/NadekoBot/data/images/cards/ten_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/10_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/ten_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/10_of_hearts.jpg b/src/NadekoBot/data/images/cards/ten_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/10_of_hearts.jpg rename to src/NadekoBot/data/images/cards/ten_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/10_of_spades.jpg b/src/NadekoBot/data/images/cards/ten_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/10_of_spades.jpg rename to src/NadekoBot/data/images/cards/ten_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/3_of_clubs.jpg b/src/NadekoBot/data/images/cards/three_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/3_of_clubs.jpg rename to src/NadekoBot/data/images/cards/three_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/3_of_diamonds.jpg b/src/NadekoBot/data/images/cards/three_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/3_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/three_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/3_of_hearts.jpg b/src/NadekoBot/data/images/cards/three_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/3_of_hearts.jpg rename to src/NadekoBot/data/images/cards/three_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/3_of_spades.jpg b/src/NadekoBot/data/images/cards/three_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/3_of_spades.jpg rename to src/NadekoBot/data/images/cards/three_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/2_of_clubs.jpg b/src/NadekoBot/data/images/cards/two_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/2_of_clubs.jpg rename to src/NadekoBot/data/images/cards/two_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/2_of_diamonds.jpg b/src/NadekoBot/data/images/cards/two_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/2_of_diamonds.jpg rename to src/NadekoBot/data/images/cards/two_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/2_of_hearts.jpg b/src/NadekoBot/data/images/cards/two_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/2_of_hearts.jpg rename to src/NadekoBot/data/images/cards/two_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/2_of_spades.jpg b/src/NadekoBot/data/images/cards/two_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/2_of_spades.jpg rename to src/NadekoBot/data/images/cards/two_of_spades.jpg diff --git a/NadekoBot/resources/images/coins/heads.png b/src/NadekoBot/data/images/coins/heads.png similarity index 100% rename from NadekoBot/resources/images/coins/heads.png rename to src/NadekoBot/data/images/coins/heads.png diff --git a/NadekoBot/resources/images/coins/tails.png b/src/NadekoBot/data/images/coins/tails.png similarity index 100% rename from NadekoBot/resources/images/coins/tails.png rename to src/NadekoBot/data/images/coins/tails.png diff --git a/NadekoBot/resources/images/dice/0.png b/src/NadekoBot/data/images/dice/0.png similarity index 100% rename from NadekoBot/resources/images/dice/0.png rename to src/NadekoBot/data/images/dice/0.png diff --git a/NadekoBot/resources/images/dice/1.png b/src/NadekoBot/data/images/dice/1.png similarity index 100% rename from NadekoBot/resources/images/dice/1.png rename to src/NadekoBot/data/images/dice/1.png diff --git a/NadekoBot/resources/images/dice/2.png b/src/NadekoBot/data/images/dice/2.png similarity index 100% rename from NadekoBot/resources/images/dice/2.png rename to src/NadekoBot/data/images/dice/2.png diff --git a/NadekoBot/resources/images/dice/3.png b/src/NadekoBot/data/images/dice/3.png similarity index 100% rename from NadekoBot/resources/images/dice/3.png rename to src/NadekoBot/data/images/dice/3.png diff --git a/NadekoBot/resources/images/dice/4.png b/src/NadekoBot/data/images/dice/4.png similarity index 100% rename from NadekoBot/resources/images/dice/4.png rename to src/NadekoBot/data/images/dice/4.png diff --git a/NadekoBot/resources/images/dice/5.png b/src/NadekoBot/data/images/dice/5.png similarity index 100% rename from NadekoBot/resources/images/dice/5.png rename to src/NadekoBot/data/images/dice/5.png diff --git a/NadekoBot/resources/images/dice/6.png b/src/NadekoBot/data/images/dice/6.png similarity index 100% rename from NadekoBot/resources/images/dice/6.png rename to src/NadekoBot/data/images/dice/6.png diff --git a/NadekoBot/resources/images/dice/7.png b/src/NadekoBot/data/images/dice/7.png similarity index 100% rename from NadekoBot/resources/images/dice/7.png rename to src/NadekoBot/data/images/dice/7.png diff --git a/NadekoBot/resources/images/dice/8.png b/src/NadekoBot/data/images/dice/8.png similarity index 100% rename from NadekoBot/resources/images/dice/8.png rename to src/NadekoBot/data/images/dice/8.png diff --git a/NadekoBot/resources/images/dice/9.png b/src/NadekoBot/data/images/dice/9.png similarity index 100% rename from NadekoBot/resources/images/dice/9.png rename to src/NadekoBot/data/images/dice/9.png diff --git a/NadekoBot/resources/images/rip.png b/src/NadekoBot/data/images/rip/rip.png similarity index 100% rename from NadekoBot/resources/images/rip.png rename to src/NadekoBot/data/images/rip/rip.png diff --git a/NadekoBot/resources/images/rip/rose_overlay.png b/src/NadekoBot/data/images/rip/rose_overlay.png similarity index 100% rename from NadekoBot/resources/images/rip/rose_overlay.png rename to src/NadekoBot/data/images/rip/rose_overlay.png diff --git a/NadekoBot/bin/Debug/data/magicitems.json b/src/NadekoBot/data/magicitems.json similarity index 100% rename from NadekoBot/bin/Debug/data/magicitems.json rename to src/NadekoBot/data/magicitems.json diff --git a/NadekoBot/bin/Debug/data/pokemon/LICENSE b/src/NadekoBot/data/pokemon/LICENSE similarity index 100% rename from NadekoBot/bin/Debug/data/pokemon/LICENSE rename to src/NadekoBot/data/pokemon/LICENSE diff --git a/NadekoBot/bin/Debug/data/pokemon/pokemon_abilities.json b/src/NadekoBot/data/pokemon/pokemon_abilities.json similarity index 100% rename from NadekoBot/bin/Debug/data/pokemon/pokemon_abilities.json rename to src/NadekoBot/data/pokemon/pokemon_abilities.json diff --git a/NadekoBot/bin/Debug/data/pokemon/pokemon_list.json b/src/NadekoBot/data/pokemon/pokemon_list.json similarity index 100% rename from NadekoBot/bin/Debug/data/pokemon/pokemon_list.json rename to src/NadekoBot/data/pokemon/pokemon_list.json diff --git a/NadekoBot/bin/Debug/data/questions.json b/src/NadekoBot/data/questions.json similarity index 99% rename from NadekoBot/bin/Debug/data/questions.json rename to src/NadekoBot/data/questions.json index a41bd6a9..b74a23a1 100644 --- a/NadekoBot/bin/Debug/data/questions.json +++ b/src/NadekoBot/data/questions.json @@ -11533,7 +11533,7 @@ }, { "Question": "What is the capital of the Canadian province of British Columbia", - "Answer": "victor1a" + "Answer": "victoria" }, { "Question": "What is the capital of the US state of Delaware", diff --git a/src/NadekoBot/data/typing_articles.json b/src/NadekoBot/data/typing_articles.json new file mode 100644 index 00000000..1a4581fe --- /dev/null +++ b/src/NadekoBot/data/typing_articles.json @@ -0,0 +1,361 @@ +[ + { + "Title":"The Gender of Psychology", + "Text":"This book addresses the diversity of psychological knowledge and practice through the lens of gender." + }, + { + "Title":"Unto Others: The Evolution and Psychology of Unselfish", + "Text":"In Unto Others philosopher Elliott Sober and biologist David Sloan Wilson demonstrate once and for all that unselfish behavior is in fact an important feature of both biological and human nature." + }, + { + "Title":"Forensic and Legal Psychology", + "Text":"Using research in clinical, cognitive, developmental, and social psychology, Forensic and Legal Psychology shows how psychological science can enhance the gathering and presentation of evidence, improve legal decision-making, prevent crime," + }, + { + "Title":"International Handbook of Psychology in Education", + "Text":"Suitable for researchers, practitioners and advisers working in the fields of psychology and education, this title presents an overview of the research within the domain of psychology of education." + }, + { + "Title":"Handbook of Personality Psychology", + "Text":"This comprehensive reference work on personality psychology discusses the development and measurement of personality, biological and social determinants, dynamic personality processes, the personality's relation to the self, and personality" + }, + { + "Title":"Dictionary of Theories, Laws, and Concepts in Psychology", + "Text":"A fully cross-referenced and source-referenced dictionary which gives definitions of psychological terms as well as the history, critique, and relevant references for the terms." + }, + { + "Title":"Essays on Plato's Psychology", + "Text":"With a comprehensive introduction to the major issues of Plato's psychology and an up-to-date bibliography of work on the relevant issues, this much-needed text makes the study of Plato's psychology accessible to scholars in ancient Greek" + }, + { + "Title":"Psychology Statistics For Dummies", + "Text":"As an alternative to typical, lead-heavy statistics texts or supplements to assigned course reading, this is one book psychology students won't want to be without." + }, + { + "Title":"Doing Psychology Experiments", + "Text":"David W. Martin’s unique blend of informality, humor, clear instruction, and solid scholarship make this concise text a popular choice for research methods courses in psychology." + }, + { + "Title":"A Handbook of Research Methods for Clinical and Health", + "Text":"For both undergraduate and postgraduate students, the book will be essential in making them aware of the full range of techniques available to them, helping them to design scientifically rigorous experiments." + }, + { + "Title":"A History of Psychology", + "Text":"First published in 2002. Routledge is an imprint of Taylor & Francis, an informa company." + }, + { + "Title":"An Introduction to the Psychology of Religion", + "Text":"The third edition of this successful book, which applies the science of psychology to problems of religion. Dr Thouless explores such questions as: why do people believe? Why are their beliefs often held with irrational strength?" + }, + { + "Title":"Psychology of Champions: How to Win at Sports and Life", + "Text":"In this unprecedented book, two psychologist researchers interview sports legends and super-athletes across sports to explain the thinking that powers stellar performers, pushing them to amazing and historic successes." + }, + { + "Title":"The Psychology of Humor: An Integrative Approach", + "Text":"This is a singly authored monograph that provides in one source, a summary of information researchers might wish to know about research into the psychology of humor." + }, + { + "Title":"Psychology and Deterrence", + "Text":"Now available in paperback, Psychology and Deterrence reveals deterrence strategy's hidden and generally simplistic assumptions about the nature of power and aggression, threat and response, and calculation and behavior in the international" + }, + { + "Title":"Psychology: An International Perspective", + "Text":"Unlike typical American texts, this book provides an international approach to introductory psychology, providing comprehensive and lively coverage of current research from a global perspective, including the UK, Germany, Scandinavia," + }, + { + "Title":"Psychology, Briefer Course", + "Text":"Despite its title, 'Psychology: Briefer Course' is more than a simple condensation of the great 'Principles of Psychology." + }, + { + "Title":"Psychology, Seventh Edition (High School)", + "Text":"This new edition continues the story of psychology with added research and enhanced content from the most dynamic areas of the field—cognition, gender and diversity studies, neuroscience and more, while at the same time using the most" + }, + { + "Title":"Psychology of Russia: Past, Present, Future", + "Text":"This book is for all psychologists and for readers whose interest in Russia exceeds their interest in psychology. Readers of this book will quickly discover a new world of thought." + }, + { + "Title":"Barron's AP Psychology", + "Text":"Provides information on scoring and structure of the test, offers tips on test-taking strategies, and includes practice examinations and subject review." + }, + { + "Title":"Psychology for Inclusive Education: New Directions in", + "Text":"International in focus and at the very cutting edge of the field, this is essential reading for all those interested in the development of inclusive education." + }, + { + "Title":"Applied Psychology: Putting Theory Into Practice", + "Text":"Applied Psychology: Putting theory into practice demonstrates how psychology theory is applied in the real world." + }, + { + "Title":"The Psychology of Science: A Reconnaissance", + "Text":"' This eBook edition contains the complete 168 page text of the original 1966 hardcover edition. Contents: Preface by Abraham H. Maslow Acknowledgments 1. Mechanistic and Humanistic Science 2." + }, + { + "Title":"Filipino American Psychology: A Handbook of Theory,", + "Text":"This book is the first of its kind and aims to promote visibility of this invisible group, so that 2.4 million Filipino Americans will have their voices heard." + }, + { + "Title":"The Psychology of Visual Illusion", + "Text":"Well-rounded perspective on the ambiguities of visual display emphasizes geometrical optical illusions: framing and contrast effects, distortion of angles and direction, and apparent 'movement' of images. 240 drawings. 1972 edition." + }, + { + "Title":"The Psychology of Women", + "Text":"This highly respected text offers students an enjoyable, extraordinarily well-written introduction to the psychology of women with an up-to-date examination of the field and comprehensive coverage of topics." + }, + { + "Title":"Psychology and Race", + "Text":"' Psychology and Race is divided into two major parts. The first half of the book looks at the interracial situation itself." + }, + { + "Title":"Psychology for A-Level", + "Text":"'Precisely targeted at AQA A Level Psychology, specification A. It will also be of interest to those who are new to psychology, and who want to get a flavour of the kinds of topics in which psychologists are interested'--Preface, p. vii." + }, + { + "Title":"Biological Psychology", + "Text":"Updated with new topics, examples, and recent research findings--and supported by new online bio-labs, part of the strongest media package yet--this text speaks to today’s students and instructors." + }, + { + "Title":"Psychology: Concepts & Connections", + "Text":"The theme of this book is applying theories and research to learning and to contemporary life." + }, + { + "Title":"The Psychology of Adoption", + "Text":"In this volume David Brodzinsky, who has conducted one of the nation's largest studies of adopted children, and Marshall Schechter, a noted child psychiatrist who has been involved with adoption related issues for over forty years, have" + }, + { + "Title":"Psychology and Adult Learning", + "Text":"This new edition is thoroughly revised and updated in light of the impact of globalising processes and the application of new information technologies, and the influence of postmodernism on psychology." + }, + { + "Title":"Gestalt Psychology: An Introduction to New Concepts in", + "Text":"The general reader, if he looks to psychology for something more than entertainment or practical advice, will discover in this book a storehouse of searching criticism and brilliant suggestions from the pen of a rare thinker, and one who" + }, + { + "Title":"The Psychology of Goals", + "Text":"Bringing together leading authorities, this tightly edited volume reviews the breadth of current knowledge about goals and their key role in human behavior." + }, + { + "Title":"Metaphors in the History of Psychology", + "Text":"Through the identification of these metaphors, the contributors to this volume have provided a remarkably useful guide to the history, current orientations, and future prospects of modern psychology." + }, + { + "Title":"Abnormal Psychology: An Integrative Approach", + "Text":"ABNORMAL PSYCHOLOGY: AN INTEGRATIVE APPROACH, Seventh Edition, is the perfect book to help you succeed in your abnormal psychology course!" + }, + { + "Title":"Art and Visual Perception: A Psychology of the Creative Eye", + "Text":"Gestalt theory and the psychology of visual perception form the basis for an analysis of art and its basic elements" + }, + { + "Title":"Psychology & Christianity: Five Views", + "Text":"This revised edition of a widely appreciated text now presents five models for understanding the relationship between psychology and Christianity." + }, + { + "Title":"The Psychology of Hope: You Can Get There from Here", + "Text":"Why do some people lead positive, hope-filled lives, while others wallow in pessimism? In The Psychology of Hope, a professor of psychology reveals the specific character traits that produce highly hopeful individuals." + }, + { + "Title":"Perspectives on Psychology", + "Text":"This is a title in the modular 'Principles in Psychology Series', designed for A-level and other introductory courses, aiming to provide students embarking on psychology courses with the necessary background and context." + }, + { + "Title":"Psychology the Easy Way", + "Text":"Material is presented in a way that makes these books ideal as self-teaching guides, but Easy Way titles are also preferred by many teachers as supplements to classroom textbooks." + }, + { + "Title":"Ethics in Psychology: Professional Standards and Cases", + "Text":"In this book, their main intent is to present the full range of contemporary ethical issues in psychology as not only relevant and intriguing, but also as integral and unavoidable aspects of the profession." + }, + { + "Title":"Psychology Gets in the Game: Sport, Mind, and Behavior,", + "Text":"The essays collected in this volume tell the stories not only of these psychologists and their subjects but of the social and academic context that surrounded them, shaping and being shaped by their ideas'--Provided by publisher." + }, + { + "Title":"Psychology for Physical Educators: Student in Focus", + "Text":"This updated edition focuses on attitude and motivation as important aspects of the physical education curriculum, illustrating practical ideas and pedagogical solutions for any PE setting." + }, + { + "Title":"The Psychology of Leadership: New Perspectives and Research", + "Text":"In this book, some of the world's leading scholars come together to describe their thinking and research on the topic of the psychology of leadership." + }, + { + "Title":"The Psychology of Interpersonal Relations", + "Text":"As the title suggests, this book examines the psychology of interpersonal relations. In the context of this book, the term 'interpersonal relations' denotes relations between a few, usually between two, people." + }, + { + "Title":"Applied Psychology", + "Text":"The chapters on Counselling Psychology and Teaching Psychology are available online via the Student Companion Site at: http://tinyurl.com/c3ztvtj The text is written to be accessible to Level 1 Introductory Psychology students, and also to" + }, + { + "Title":"Psychology", + "Text":"An exciting read for anyone interested in psychology and research; because of its comprehensive appendix, glossary, and reference section, this book is a must-have desk reference for psychologists and others in the field." + }, + { + "Title":"The Psychology of Music", + "Text":"On interpreting musical phenomena in terms of mental function" + }, + { + "Title":"Abnormal Psychology", + "Text":"Ron Comer's Abnormal Psychology continues to captivate students with its integrated coverage of theory, diagnosis, and treatment, its inclusive wide-ranging cross-cultural perspective, and its compassionate emphasis on the real impact of" + }, + { + "Title":"The Psychology of Food Choice", + "Text":"This book brings together theory, research and applications from psychology and behavioural sciences applied to dietary behaviour." + }, + { + "Title":"Psychology: brain, behavior, & culture", + "Text":"Rather than present psychological science as a series of facts for memorization, this book takes readers on a psychological journey that uncovers things they didn't know or new ways of thinking about things they did know." + }, + { + "Title":"A Brief History of Psychology", + "Text":"Due to its brevity and engaging style, the book is often used in introductory courses to introduce students to the field. The enormous index and substantial glossary make this volume a useful desk reference for the entire field." + }, + { + "Title":"Psychology AS: The Complete Companion", + "Text":"Presented in double-page spreads this book written to the average AS ability level, provides information on psychology in bite-sized chunks with learning and revision features." + }, + { + "Title":"The Psychology Book: From Shamanism to Cutting-Edge", + "Text":"Lavishly illustrated, this new addition in the Sterling's Milestones series chronicles the history of psychology through 250 groundbreaking events, theories, publications, experiments and discoveries." + }, + { + "Title":"The Psychology Book", + "Text":"All the big ideas, simply explained - an innovative and accessible guide to the study of human nature The Psychology Book clearly explains more than 100 groundbreaking ideas in this fascinating field of science." + }, + { + "Title":"Handbook of Positive Psychology", + "Text":"' The Handbook of Positive Psychology provides a forum for a more positive view of the human condition. In its pages, readers are treated to an analysis of what the foremost experts believe to be the fundamental strengths of humankind." + }, + { + "Title":"Psychology of Sustainable Development", + "Text":"With contributions from an international team of policy shapers and makers, the book will be an important reference for environmental, developmental, social, and organizational psychologists, in addition to other social scientists concerned" + }, + { + "Title":"An Introduction to the History of Psychology", + "Text":"In this Fifth Edition, B.R. Hergenhahn demonstrates that most of the concerns of contemporary psychologists are manifestations of themes that have been part of psychology for hundreds-or even thousands-of years." + }, + { + "Title":"Careers in Psychology: Opportunities in a Changing World", + "Text":"This text addresses the growing need among students and faculty for information about the careers available in psychology at the bachelor’s and graduate level." + }, + { + "Title":"Philosophy of Psychology", + "Text":"This is the story of the clattering of elevated subways and the cacophony of crowded neighborhoods, the heady optimism of industrial progress and the despair of economic recession, and the vibrancy of ethnic cultures and the resilience of" + }, + { + "Title":"The Psychology of Risk Taking Behavior", + "Text":"This book aims to help the reader to understand what motivates people to engage in risk taking behavior, such as participating in traffic, sports, financial investments, or courtship." + }, + { + "Title":"The Nazi Doctors: Medical Killing and the Psychology of", + "Text":"This book explores the psychological conditions that promote the human potential for evil, relating medical killing to broader principles of doubling and genocide" + }, + { + "Title":"The Body and Psychology", + "Text":"The material in this volume was previously published as a Special Issue of th" + }, + { + "Title":"Introduction to Psychology: Gateways to Mind and Behavior", + "Text":"Important Notice: Media content referenced within the product description or the product text may not be available in the ebook version." + }, + { + "Title":"Psychology of Time", + "Text":"Basic Structure The book would contain 14 or 15 chapters of roughly 12 000 words. The exact final number of chapters would depend on further discussions with you about the book's basic structure." + }, + { + "Title":"Handbook of Psychology, Experimental Psychology", + "Text":"Includes established theories and cutting-edge developments. Presents the work of an international group of experts. Presents the nature, origin, implications, and future course of major unresolved issues in the area." + }, + { + "Title":"Study Guide for Psychology, Seventh Edition", + "Text":"This new edition continues the story of psychology with added research and enhanced content from the most dynamic areas of the field--cognition, gender and diversity studies, neuroscience and more, while at the same time using the most" + }, + { + "Title":"Culture and Psychology", + "Text":"In addition, the text encourages students to question traditionally held beliefs and theories as and their relevance to different cultural groups today." + }, + { + "Title":"Exploring the Psychology of Interest", + "Text":"The most comprehensive work of its kind, Exploring the Psychology of Interest will be a valuable resource for student and professional researchers in cognitive, social, and developmental psychology." + }, + { + "Title":"Handbook of Adolescent Psychology", + "Text":"The study of adolescence in the field of psychology has grown tremendously over the last two decades, necessitating a comprehensive and up-to-date revision of this seminal work." + }, + { + "Title":"The Psychology of Diplomacy", + "Text":"World class clinicians, researchers, and activists present the psychological dimensions to diplomacy drawn from examples set in the United Nations, Camp David, the Middle East, Japan, South Africa, and elsewhere." + }, + { + "Title":"The Psychology of Social Class", + "Text":"By addressing differences in social class, the book broadens the perspective of social psychological research to examine such topics as the effect of achievement motivation and other personality variables on social mobility and the effect" + }, + { + "Title":"Applied Psychology: Current Issues and New Directions", + "Text":"Key features of this book: - Consistently pedagogical throughout - chapter summaries, questions for reflection and discussion and annotated further reading in every chapter - Comprehensive coverage - all areas of applied psychology included" + }, + { + "Title":"Popular Psychology: An Encyclopedia", + "Text":"Entries cover a variety of topics in the field of popular psychology, including acupuncture, emotional intelligence, brainwashing, chemical inbalance, and seasonal affective disorder." + }, + { + "Title":"Advanced Psychology: Applications, Issues and Perspectives", + "Text":"The second of two books, Advanced Psychology covers units 4 to 6 for the second year at Advanced Level." + }, + { + "Title":"Mindset: The New Psychology of Success", + "Text":"This is a book that can change your life, as its ideas have changed mine.”—Robert J. Sternberg, IBM Professor of Education and Psychology at Yale University, director of the PACE Center of Yale University, and author of Successful" + }, + { + "Title":"E-Z Psychology", + "Text":"This book covers material as it is taught on a college-101 level." + }, + { + "Title":"Myers' Psychology for AP*", + "Text":"Already The Bestselling AP* Psychology Author, Myers Writes His First Exclusive AP* Psych Text Watch Dave G. Myers introduce this new text here." + }, + { + "Title":"Psychology and Health", + "Text":"Part of a series of textbooks which have been written to support A levels in psychology. The books use real life applications to make theories come alive for students and teach them what they need to know." + }, + { + "Title":"Applying Psychology in Business: The Handbook for Managers", + "Text":"To learn more about Rowman & Littlefield titles please visit us at www.rowmanlittlefield.com." + }, + { + "Title":"Influence", + "Text":"Influence, the classic book on persuasion, explains the psychology of why people say 'yes'—and how to apply these understandings. Dr. Robert Cialdini is the seminal expert in the rapidly expanding field of influence and persuasion." + }, + { + "Title":"Psychology and Policing", + "Text":"The book should draw attention to the often unrecognized and valuable contribution that mainstream psychology can make to the knowledge base underpinning a wide variety of policing practices." + }, + { + "Title":"Applied Psychology: New Frontiers and Rewarding Careers", + "Text":"This book examines how psychological science is, and can be, used to prevent and ameliorate pressing human problems to promote positive social change." + }, + { + "Title":"Psychology: Concepts and Applications", + "Text":"Nevid developed the effective teaching devices in this text based on a comprehensive system derived from research on learning and memory as well as his own research on textbook pedagogy." + }, + { + "Title":"Foundations of Sport and Exercise Psychology, 6E: ", + "Text":"This text offers both students and new practitioners a comprehensive view of sport and exercise psychology, drawing connections between research and practice and capturing the excitement of the world of sport and exercise." + }, + { + "Title":"Biographical Dictionary of Psychology", + "Text":"This Dictionary provides biographical and bibliographical information on over 500 psychologists from all over the world from 1850 to the present day. All branches of psychology and its related disciplines are featured." + }, + { + "Title":"Psychology: A Self-Teaching Guide", + "Text":"Frank Bruno explains all the major psychological theories and terms in this book, covering perception, motivation, thinking, personality, sensation, intelligence, research methods, and much more." + }, + { + "Title":"A Dictionary of Psychology", + "Text":"Entries are extensively cross-referenced for ease of use, and cover word origins and derivations as well as definitions. Over 80 illustrations complement the text." + }, + { + "Title":"An Intellectual History of Psychology", + "Text":"Invaluable as a text for students and as a stimulating and insightful overview for scholars and practicing psychologists, this volume can be read either as a history of psychology in both its philosophical and aspiring scientific periods or" + }] \ No newline at end of file diff --git a/src/NadekoBot/data/units.json b/src/NadekoBot/data/units.json new file mode 100644 index 00000000..1b5cf769 --- /dev/null +++ b/src/NadekoBot/data/units.json @@ -0,0 +1,988 @@ +[ + { + "Triggers": [ + "millimeter", + "millimeters", + "millimeter", + "mm" + ], + "UnitType": "length", + "Modifier": 0.001 + }, + { + "Triggers": [ + "centimeter", + "centimeters", + "centimeter", + "cm" + ], + "UnitType": "length", + "Modifier": 0.01 + }, + { + "Triggers": [ + "decimeter", + "decimeters", + "decimeter", + "dm" + ], + "UnitType": "length", + "Modifier": 0.1 + }, + { + "Triggers": [ + "meter", + "meters", + "meter", + "m" + ], + "UnitType": "length", + "Modifier": 1.0 + }, + { + "Triggers": [ + "kilometer", + "kilometers", + "kilometer", + "km" + ], + "UnitType": "length", + "Modifier": 1000.0 + }, + { + "Triggers": [ + "foot", + "feet", + "foot", + "ft" + ], + "UnitType": "length", + "Modifier": 0.3048 + }, + { + "Triggers": [ + "inch", + "inches", + "inch", + "in" + ], + "UnitType": "length", + "Modifier": 0.0254 + }, + { + "Triggers": [ + "mile", + "miles", + "mile", + "mi" + ], + "UnitType": "length", + "Modifier": 1609.344 + }, + { + "Triggers": [ + "yard", + "yards", + "yard", + "yd" + ], + "UnitType": "length", + "Modifier": 0.9144 + }, + { + "Triggers": [ + "cubic foot", + "cubic feet", + "cubic foot", + "ft3" + ], + "UnitType": "volume", + "Modifier": 0.02831685 + }, + { + "Triggers": [ + "cubic inch", + "cubic inches", + "cubic inch", + "in3" + ], + "UnitType": "volume", + "Modifier": 0.00001638706 + }, + { + "Triggers": [ + "cubic mile", + "cubic miles", + "cubic mile", + "mi3" + ], + "UnitType": "volume", + "Modifier": 4168182000.0 + }, + { + "Triggers": [ + "cubic yard", + "cubic yards", + "cubic yard", + "yd3" + ], + "UnitType": "volume", + "Modifier": 0.7645549 + }, + { + "Triggers": [ + "cup", + "cups", + "cup", + "cup" + ], + "UnitType": "volume", + "Modifier": 0.0002365882 + }, + { + "Triggers": [ + "imperial gallon", + "Imperial gallons", + "Imperial gallon", + "gal" + ], + "UnitType": "volume", + "Modifier": 0.00454609 + }, + { + "Triggers": [ + "us gallon", + "US gallons", + "US gallon", + "gal" + ], + "UnitType": "volume", + "Modifier": 0.003785412 + }, + { + "Triggers": [ + "milliliter", + "milliliters", + "milliliter", + "mL" + ], + "UnitType": "volume", + "Modifier": 0.000001 + }, + { + "Triggers": [ + "liter", + "liters", + "liter", + "L" + ], + "UnitType": "volume", + "Modifier": 0.001 + }, + { + "Triggers": [ + "imperial fluid ounce", + "Imperial fluid ounces", + "Imperial fluid ounce", + "fl oz" + ], + "UnitType": "volume", + "Modifier": 0.00002841306 + }, + { + "Triggers": [ + "us fluid ounce", + "US fluid ounces", + "US fluid ounce", + "fl oz" + ], + "UnitType": "volume", + "Modifier": 0.00002957353 + }, + { + "Triggers": [ + "imperial pint", + "Imperial pints", + "Imperial pint", + "pt" + ], + "UnitType": "volume", + "Modifier": 0.00056826125 + }, + { + "Triggers": [ + "us liquid pint", + "US pints (liquid)", + "US pint (liquid)", + "pt" + ], + "UnitType": "volume", + "Modifier": 0.0004731765 + }, + { + "Triggers": [ + "us dry pint", + "US pints (dry)", + "US pint (dry)", + "pt" + ], + "UnitType": "volume", + "Modifier": 0.0005506105 + }, + { + "Triggers": [ + "imperial quart", + "Imperial quarts", + "Imperial quart", + "qt" + ], + "UnitType": "volume", + "Modifier": 0.00113652297 + }, + { + "Triggers": [ + "us liquid quart", + "US quarts (liquid)", + "US quart (liquid)", + "qt" + ], + "UnitType": "volume", + "Modifier": 0.0009463529 + }, + { + "Triggers": [ + "us dry quart", + "US quarts (dry)", + "US quart (dry)", + "qt" + ], + "UnitType": "volume", + "Modifier": 0.001101221 + }, + { + "Triggers": [ + "tablespoon", + "tablespoons", + "tablespoon", + "tbsp" + ], + "UnitType": "volume", + "Modifier": 0.00001478676 + }, + { + "Triggers": [ + "teaspoon", + "teaspoons", + "teaspoon", + "tspn" + ], + "UnitType": "volume", + "Modifier": 0.000004928922 + }, + { + "Triggers": [ + "milligram", + "milligrams", + "milligram", + "mg" + ], + "UnitType": "weight", + "Modifier": 0.000001 + }, + { + "Triggers": [ + "gram", + "grams", + "gram", + "g" + ], + "UnitType": "weight", + "Modifier": 0.001 + }, + { + "Triggers": [ + "kilogram", + "kilograms", + "kilogram", + "kg" + ], + "UnitType": "weight", + "Modifier": 1.0 + }, + { + "Triggers": [ + "carat", + "carats", + "carat", + "CD" + ], + "UnitType": "weight", + "Modifier": 0.00020 + }, + { + "Triggers": [ + "grain", + "grains", + "grain", + "gr" + ], + "UnitType": "weight", + "Modifier": 0.00006479891 + }, + { + "Triggers": [ + "ounce", + "ounces", + "ounce", + "oz" + ], + "UnitType": "weight", + "Modifier": 0.02834952 + }, + { + "Triggers": [ + "pennyweight", + "pennyweights", + "pennyweight", + "dwt" + ], + "UnitType": "weight", + "Modifier": 0.001555174 + }, + { + "Triggers": [ + "pound", + "pounds", + "pound", + "lb" + ], + "UnitType": "weight", + "Modifier": 0.4535924 + }, + { + "Triggers": [ + "stone", + "stones", + "stone", + "st" + ], + "UnitType": "weight", + "Modifier": 6.35029318 + }, + { + "Triggers": [ + "slug", + "slugs", + "slug", + "slug" + ], + "UnitType": "weight", + "Modifier": 14.59390 + }, + { + "Triggers": [ + "metric ton", + "metric tons", + "metric ton", + "t" + ], + "UnitType": "weight", + "Modifier": 1000.0 + }, + { + "Triggers": [ + "long ton", + "long tons", + "long ton", + "t" + ], + "UnitType": "weight", + "Modifier": 1016.047 + }, + { + "Triggers": [ + "short ton", + "short tons", + "short ton", + "t" + ], + "UnitType": "weight", + "Modifier": 907.1847 + }, + { + "Triggers": [ + "acre", + "acres", + "acre", + "acre" + ], + "UnitType": "area", + "Modifier": 4046.873 + }, + { + "Triggers": [ + "are", + "ares", + "are", + "a" + ], + "UnitType": "area", + "Modifier": 100.0 + }, + { + "Triggers": [ + "hectare", + "hectares", + "hectare", + "ha" + ], + "UnitType": "area", + "Modifier": 10000.0 + }, + { + "Triggers": [ + "square foot", + "square feet", + "square foot", + "ft2" + ], + "UnitType": "area", + "Modifier": 0.09290304 + }, + { + "Triggers": [ + "square meter", + "square meters", + "square meter", + "m2" + ], + "UnitType": "area", + "Modifier": 1.0 + }, + { + "Triggers": [ + "square kilometer", + "square kilometers", + "square kilometer", + "km2" + ], + "UnitType": "area", + "Modifier": 1000000.0 + }, + { + "Triggers": [ + "square inch", + "square inches", + "square inch", + "in2" + ], + "UnitType": "area", + "Modifier": 0.00064516 + }, + { + "Triggers": [ + "square yard", + "square yards", + "square yard", + "yd2" + ], + "UnitType": "area", + "Modifier": 0.8361274 + }, + { + "Triggers": [ + "square mile", + "square miles", + "square mile", + "mi2" + ], + "UnitType": "area", + "Modifier": 2589988.0 + }, + { + "Triggers": [ + "aankadam", + "aankadam", + "aankadam", + "aankadam" + ], + "UnitType": "area", + "Modifier": 6.69 + }, + { + "Triggers": [ + "perch", + "perches", + "perch", + "perch" + ], + "UnitType": "area", + "Modifier": 25.29 + }, + { + "Triggers": [ + "cent", + "cents", + "cent", + "cent" + ], + "UnitType": "area", + "Modifier": 40.47 + }, + { + "Triggers": [ + "chatak", + "chataks", + "chatak", + "chatak" + ], + "UnitType": "area", + "Modifier": 41.81 + }, + { + "Triggers": [ + "kottah", + "kottah (B)", + "kottah (B)", + "kottah (B)" + ], + "UnitType": "area", + "Modifier": 66.89 + }, + { + "Triggers": [ + "guntha", + "guntha", + "guntha", + "guntha" + ], + "UnitType": "area", + "Modifier": 101.17 + }, + { + "Triggers": [ + "ground", + "grounds", + "ground", + "ground" + ], + "UnitType": "area", + "Modifier": 222.97 + }, + { + "Triggers": [ + "marla", + "marla", + "marla", + "marla" + ], + "UnitType": "area", + "Modifier": 501.68 + }, + { + "Triggers": [ + "rood", + "roods", + "rood", + "rood" + ], + "UnitType": "area", + "Modifier": 1011.71 + }, + { + "Triggers": [ + "bigha I", + "bigha I", + "bigha I", + "bigha I" + ], + "UnitType": "area", + "Modifier": 1618.74 + }, + { + "Triggers": [ + "bigha II", + "bigha II", + "bigha II", + "bigha II" + ], + "UnitType": "area", + "Modifier": 2529.29 + }, + { + "Triggers": [ + "kanal", + "kanal", + "kanal", + "kanal" + ], + "UnitType": "area", + "Modifier": 10033.53 + }, + { + "Triggers": [ + "biswa I", + "biswa I", + "biswa I", + "biswa I" + ], + "UnitType": "area", + "Modifier": 32374.85 + }, + { + "Triggers": [ + "biswa II", + "biswa II", + "biswa II", + "biswa II" + ], + "UnitType": "area", + "Modifier": 50585.71 + }, + { + "Triggers": [ + "pascal", + "pascal", + "pascal", + "Pa" + ], + "UnitType": "pressure", + "Modifier": 1.0 + }, + { + "Triggers": [ + "torr", + "torr", + "torr", + "Torr" + ], + "UnitType": "pressure", + "Modifier": 133.3224 + }, + { + "Triggers": [ + "bar", + "bars", + "bar", + "bar" + ], + "UnitType": "pressure", + "Modifier": 100000.0 + }, + { + "Triggers": [ + "millibar", + "millibars", + "millibar", + "mb" + ], + "UnitType": "pressure", + "Modifier": 100.0 + }, + { + "Triggers": [ + "psi", + "psi", + "psi", + "lbf/in2" + ], + "UnitType": "pressure", + "Modifier": 6894.757 + }, + { + "Triggers": [ + "day", + "days", + "day", + "d" + ], + "UnitType": "time", + "Modifier": 86400.0 + }, + { + "Triggers": [ + "hour", + "hours", + "hour", + "h" + ], + "UnitType": "time", + "Modifier": 3600.0 + }, + { + "Triggers": [ + "minute", + "minutes", + "minute", + "min" + ], + "UnitType": "time", + "Modifier": 60.0 + }, + { + "Triggers": [ + "year", + "years", + "year", + "yr" + ], + "UnitType": "time", + "Modifier": 31536000.0 + }, + { + "Triggers": [ + "AUD" + ], + "UnitType": "currency", + "Modifier": 1.4787 + }, + { + "Triggers": [ + "BGN" + ], + "UnitType": "currency", + "Modifier": 1.9558 + }, + { + "Triggers": [ + "BRL" + ], + "UnitType": "currency", + "Modifier": 3.5991 + }, + { + "Triggers": [ + "CAD" + ], + "UnitType": "currency", + "Modifier": 1.4562 + }, + { + "Triggers": [ + "CHF" + ], + "UnitType": "currency", + "Modifier": 1.0951 + }, + { + "Triggers": [ + "CNY" + ], + "UnitType": "currency", + "Modifier": 7.4565 + }, + { + "Triggers": [ + "CZK" + ], + "UnitType": "currency", + "Modifier": 27.025 + }, + { + "Triggers": [ + "DKK" + ], + "UnitType": "currency", + "Modifier": 7.4448 + }, + { + "Triggers": [ + "GBP" + ], + "UnitType": "currency", + "Modifier": 0.8517 + }, + { + "Triggers": [ + "HKD" + ], + "UnitType": "currency", + "Modifier": 8.6631 + }, + { + "Triggers": [ + "HRK" + ], + "UnitType": "currency", + "Modifier": 7.4846 + }, + { + "Triggers": [ + "HUF" + ], + "UnitType": "currency", + "Modifier": 308.97 + }, + { + "Triggers": [ + "IDR" + ], + "UnitType": "currency", + "Modifier": 14814.35 + }, + { + "Triggers": [ + "ILS" + ], + "UnitType": "currency", + "Modifier": 4.2241 + }, + { + "Triggers": [ + "INR" + ], + "UnitType": "currency", + "Modifier": 74.8703 + }, + { + "Triggers": [ + "JPY" + ], + "UnitType": "currency", + "Modifier": 114.27 + }, + { + "Triggers": [ + "KRW" + ], + "UnitType": "currency", + "Modifier": 1244.47 + }, + { + "Triggers": [ + "MXN" + ], + "UnitType": "currency", + "Modifier": 20.7542 + }, + { + "Triggers": [ + "MYR" + ], + "UnitType": "currency", + "Modifier": 4.5205 + }, + { + "Triggers": [ + "NOK" + ], + "UnitType": "currency", + "Modifier": 9.2873 + }, + { + "Triggers": [ + "NZD" + ], + "UnitType": "currency", + "Modifier": 1.5427 + }, + { + "Triggers": [ + "PHP" + ], + "UnitType": "currency", + "Modifier": 51.797 + }, + { + "Triggers": [ + "PLN" + ], + "UnitType": "currency", + "Modifier": 4.3436 + }, + { + "Triggers": [ + "RON" + ], + "UnitType": "currency", + "Modifier": 4.4505 + }, + { + "Triggers": [ + "RUB" + ], + "UnitType": "currency", + "Modifier": 72.4564 + }, + { + "Triggers": [ + "SEK" + ], + "UnitType": "currency", + "Modifier": 9.5008 + }, + { + "Triggers": [ + "SGD" + ], + "UnitType": "currency", + "Modifier": 1.5196 + }, + { + "Triggers": [ + "THB" + ], + "UnitType": "currency", + "Modifier": 38.608 + }, + { + "Triggers": [ + "TRY" + ], + "UnitType": "currency", + "Modifier": 3.2977 + }, + { + "Triggers": [ + "USD" + ], + "UnitType": "currency", + "Modifier": 1.1168 + }, + { + "Triggers": [ + "ZAR" + ], + "UnitType": "currency", + "Modifier": 16.0537 + }, + { + "Triggers": [ + "K", + "kelvin" + ], + "UnitType": "temperature", + "Modifier": 0.00 + }, + { + "Triggers": [ + "F", + "fahrenheit" + ], + "UnitType": "temperature", + "Modifier": 0.00 + }, + { + "Triggers": [ + "C", + "Celcius", + "Centigrade" + ], + "UnitType": "temperature", + "Modifier": 0.00 + }, + { + "Triggers": [ + "EUR" + ], + "UnitType": "currency", + "Modifier": 1.0 + }, + { + "Triggers": [ + "SKK" + ], + "UnitType": "currency", + "Modifier": 30.13 + } +] diff --git a/NadekoBot/bin/Debug/data/wowjokes.json b/src/NadekoBot/data/wowjokes.json similarity index 100% rename from NadekoBot/bin/Debug/data/wowjokes.json rename to src/NadekoBot/data/wowjokes.json diff --git a/src/NadekoBot/libopus.so b/src/NadekoBot/libopus.so new file mode 100755 index 00000000..2fb07c31 Binary files /dev/null and b/src/NadekoBot/libopus.so differ diff --git a/src/NadekoBot/libsodium.dll b/src/NadekoBot/libsodium.dll new file mode 100644 index 00000000..f5de830e Binary files /dev/null and b/src/NadekoBot/libsodium.dll differ diff --git a/src/NadekoBot/libsodium.so b/src/NadekoBot/libsodium.so new file mode 100755 index 00000000..548285a6 Binary files /dev/null and b/src/NadekoBot/libsodium.so differ diff --git a/src/NadekoBot/opus.dll b/src/NadekoBot/opus.dll new file mode 100644 index 00000000..a962869f Binary files /dev/null and b/src/NadekoBot/opus.dll differ diff --git a/src/NadekoBot/project.json b/src/NadekoBot/project.json new file mode 100644 index 00000000..b5fd1cbb --- /dev/null +++ b/src/NadekoBot/project.json @@ -0,0 +1,67 @@ +{ + "version": "1.0.0-*", + "description": "General purpose Discord bot written in C#.", + "authors": [ "Kwoth" ], + "copyright": "Kwoth", + "buildOptions": { + "emitEntryPoint": true, + "allowUnsafe": true, + "compile": { + "exclude": [ "data", "credentials.json", "credentials_example.json", "Modules/Music/Classes/PlaylistFullException.cs" ] + }, + "copyToOutput": { + "include": [ "data" ], + "includeFiles": [ "libsodium.dll", "opus.dll", "libsodium.so", "libopus.so" ], + "exclude": [ "data/musicdata" ] + }, + "define": [] + }, + "dependencies": { + "VideoLibrary": "1.3.4", + "CoreCLR-NCalc": "2.1.2", + "Discord.Net.Commands": { + "target": "project" + }, + "Discord.Net": { + "target": "project" + }, + "Google.Apis.Urlshortener.v1": "1.19.0.138", + "Google.Apis.YouTube.v3": "1.19.0.655", + "ImageSharp": "1.0.0-alpha-000079", + "Microsoft.EntityFrameworkCore": "1.1.0", + "Microsoft.EntityFrameworkCore.Design": "1.1.0", + "Microsoft.EntityFrameworkCore.Sqlite": "1.1.0", + "Microsoft.Extensions.Configuration": "1.1.0", + "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0", + "Microsoft.Extensions.Configuration.Json": "1.1.0", + "Microsoft.Extensions.DependencyInjection": "1.1.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "1.1.0", + "Microsoft.Extensions.PlatformAbstractions": "1.1.0", + "Microsoft.NETCore.App": { + "type": "platform", + "version": "1.1.0" + }, + "Newtonsoft.Json": "9.0.2-beta1", + "NLog": "5.0.0-beta03", + "System.Diagnostics.Contracts": "4.3.0", + "System.Xml.XPath": "4.3.0" + }, + "tools": { + "Microsoft.EntityFrameworkCore.Tools.DotNet": "1.1.0-preview4-final", + "Microsoft.DotNet.Watcher.Tools": "1.1.0-preview4-final" + }, + "frameworks": { + "netcoreapp1.0": { + "imports": [ + "dnxcore50", + "portable-net45+win8+wpa81" + ] + } + }, + "configurations": { + "GlobalNadeko": { + "buildOptions": { "define": [ "GLOBAL_NADEKO" ] }, + "compilationOptions": { "optimize": true } + } + } +}