diff --git a/.gitignore b/.gitignore index ff2c4dd52..7cdc81825 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -## Ignore Visual Studio temporary files, build results, and +### Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files @@ -19,10 +19,10 @@ obj/ !**/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/currency_images/* +WizBot/bin/debug/*.* +WizBot/bin/debug/data/permissions +WizBot/bin/debug/data/incidents +!WizBot/bin/Debug/data/currency_images/* Tests/bin # NuGet Packages @@ -33,6 +33,8 @@ 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 \ No newline at end of file +WizBot/bin/Debug/data/WizBot.sqlite +WizBot/bin/Debug/data/config.json +WizBot/bin/Debug/data/ServerSpecificConfigs.json +/BuildProcessTemplates +/.vs/config diff --git a/.gitmodules b/.gitmodules index 9ff1a2975..a45a5dd4e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "discord.net"] path = discord.net - url = git://github.com/rogueexception/discord.net.git + url = git://github.com/rogueexception/discord.net.git \ No newline at end of file diff --git a/09.md b/09.md new file mode 100644 index 000000000..b63ba696b --- /dev/null +++ b/09.md @@ -0,0 +1 @@ +0.9 diff --git a/ComprehensiveGuide.md b/ComprehensiveGuide.md index 2e5f2fee3..762e2caf3 100644 --- a/ComprehensiveGuide.md +++ b/ComprehensiveGuide.md @@ -2,38 +2,39 @@ ________________________________________________________________________________ *Thanks to @Flatbread for making this guide* ________________________________________________________________________________ -#### Setting Up NadekoBot v0.93.2 +#### Setting Up WizBot v0.93.2 ###### Prerequisites: 1) NET Framework 4.5.2 (or 4.6) +- Download WizBot - Rename credentials_example.json into credentials.json. (Note: If you do not see a .json after credentials_example.json, do not add the .json. You likely have "Hide file extensions" as enabled.) - Go to (https://discordapp.com/developers/applications/me). Log in if you have to with your Discord account. Press "New Application" and fill out an App Name and, optionally, an app description and icon. Afterwards, create the application. Once the application is created, click on "Create a Bot User" and confirm it. You will then see the bot's username, ID and token. Reveal and copy the token and the bot ID. - Open up credentials.json. Paste the token into the Token field, between the quotes. Paste the ID into the BotID field. Leave email and password fields empty. Save and close credentials.json. -- Go into data folder and make sure you have config.json file. If there is no config.json, rename the config_example.json to config.json. -- Copy your CLIENT ID (that's in the same Developer page where you brought your token) and replace `12345678` in the this link: +- Go into data folder and make sure you have config.json file. If there is no config.json, rename the config_example.json to config.json. [REFERENCE IMAGE](https://cdn.discordapp.com/attachments/117523346618318850/178813495872192513/unknown.png) +- 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. -- Start NadekoBot.exe. In a text channel, **not a direct message**, type in [.uid @______] without the brackets, filling in the underlined portion with your name and send the message. Your bot will reply with a number; this is your ID. Copy this ID and close NadekoBot.exe. +- Start WizBot.exe. In a text channel, **not a direct message**, type in [.uid @______] without the brackets, filling in the underlined portion with your name and send the message. Your bot will reply with a number; this is your ID. Copy this ID and close WizBot.exe. - Reopen credentials.json. Paste your ID into the square brackets ("OwnerIds": [1231312313]). You can add multiple owners by separating IDs with a comma. Close and save credentials.json. ________________________________________________________________________________ -#### Setting Up NadekoBot For Music +#### Setting Up WizBot For Music ###### Prerequisites: 1) FFMPEG, Static Build Version (See below) Google Account -2) Soundcloud Account +2) Soundcloud Account (if you want soundcloud support) - Download FFMPEG through the link (https://ffmpeg.zeranoe.com/builds/). - 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\\bin**). Save and close. - Go to console.developers.google.com 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". Under the "YouTube APIs" section, enable "YouTube Data API". +- Under the "Other Popular APIs" section, enable "URL Shortener API". Under the "YouTube APIs" section, enable "YouTube Data API". Also enable Custom Search Api. - On the left tab, access Credentials. There will be a line saying "If you wish to skip this step and create an API key, client ID or service account." 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. - Go to (https://soundcloud.com/you/apps/new). 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. ________________________________________________________________________________ -#### Setting Up NadekoBot Permissions -###### NadekoBot's permissions can be set up to be very specific through commands in the Permissions module. +#### Setting Up WizBot Permissions +###### WizBot'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: - a user level (so specific users can or cannot use a command/module) - a role level (so only certain roles have access to certain commands/module) @@ -75,4 +76,4 @@ Check permissions by using the letter of the level you want to check followed by 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. +Reference the Help command (-h) for more Permissions related commands. \ No newline at end of file diff --git a/LinuxSetup.md b/LinuxSetup.md new file mode 100644 index 000000000..f5c9d454b --- /dev/null +++ b/LinuxSetup.md @@ -0,0 +1,256 @@ +#SETTING UP WIZBOT ON LINUX UBUNTU 14+ + +######If you want WizBot 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 WizBot on Wiz VPS KVM server using the link http://wiz-vps.com/vps-hosting-plans/kvm/ + + +Assuming you have followed the link above to created an account in Wiz VPS and until you get the `IP address and root password (in email)` to login, its time to begin: + +**DOWNLOAD PuTTY** + +http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html + +**DOWNLOAD and INSTALL CyberDuck** `(for accessing filesystem using SFTP)` + +https://cyberduck.io + + + +**Follow the steps below:** + +**Open PuTTY.exe** that you downloaded before, and paste or enter your `IP address` and then click **Open** + +If you entered your VPS 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 Wiz VPS**, 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: http://www.mono-project.com/docs/getting-started/install/linux/) + +**1)** + +
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 be 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
+
+ +**3)** +
apt-get install mono-devel
+
+**Type** `y` **hit Enter** +######Opus Voice Codec + +**4)** +
sudo apt-get install libopus0 opus-tools
+
+**Type** `y` **hit Enter** + +**5)** +
sudo apt-get install libopus-dev
+
+ +######FFMPEG + +**6)** +
apt-get install ffmpeg
+
+**Type** `y` **hit Enter** + +`NOTE: if its "not installing" then, follow the guide here:` http://www.faqforge.com/linux/how-to-install-ffmpeg-on-ubuntu-14-04/ + +*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` + +######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
+
+ +######Unzip + +**11)** +
apt-get install unzip
+
+ +######TMUX +**12)** +
apt-get install tmux
+
+**Type** `y` **hit Enter** + +######NOW WE NEED TO IMPORT SOME DISCORD CERTS +**13)** +
mozroots --import --ask-remove --machine
+
+ +**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 “wizbot” or anything you prefer +
mkdir wizbot
+
+ +**16)** + +Move to “wizbot” folder (note `cd --` to go back the directory) +
cd wizbot
+
+ +**NOW WE NEED TO GET WIZBOT FROM RELEASES** + + +Go to this link: https://github.com/Wizkiller96/WizBot-Updated/releases and **copy the zip file address** of the lalest version available, + +it should look like `https://github.com/Wizkiller96/WizBot-Updated/releases/download/vx.xx/WizBot.vx.x.zip` + +**17)** + +Get the correct link, type `wget`, then *paste the link*, then hit **Enter**. +
wget https://github.com/Wizkiller96/WizBot-Updated/releases/download/vx.xx/WizBot.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 WizBot.vx.x.zip
+
+**^Do not copy-paste it** + +######NOW TO SETUP WIZBOT + +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: https://github.com/Wizkiller96/WizBot-Updated/blob/master/README.md + +Rename it to `credentials.json` and paste/put it back in the folder. `(Yes, using CyberDuck)` + +You should see two files `credentials_example.json` and `credentials.json` + +Also if you already have wizbot setup and have `credentials.json`, `config.json`, `WizBot.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 wizbot
+
+**^this will create a new session named “wizbot”** `(you can replace “wizbot” 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 wizbot
+
+ +**20)** + +
mono WizBot.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), and you can finally close PuTTY now. + +**NOW YOU HAVE YOUR OWN WIZBOT** + +######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 wizbot` (**wizbot** is the name of the session we created before so, replace **“wizbot”** with the session name you created.) + +**21)** + +-If you want to **kill** WizBot **session**, type `tmux kill-session -t wizbot` + +######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+D** and close **PuTTY** diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs deleted file mode 100644 index 6ee9c3773..000000000 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ /dev/null @@ -1,233 +0,0 @@ -using Discord; -using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Modules.Administration.Commands -{ - internal class LogCommand : DiscordCommand - { - - private readonly ConcurrentDictionary logs = new ConcurrentDictionary(); - private readonly ConcurrentDictionary loggingPresences = new ConcurrentDictionary(); - private readonly ConcurrentDictionary voiceChannelLog = new ConcurrentDictionary(); - - public LogCommand(DiscordModule module) : base(module) - { - NadekoBot.Client.MessageReceived += MsgRecivd; - NadekoBot.Client.MessageDeleted += MsgDltd; - NadekoBot.Client.MessageUpdated += MsgUpdtd; - NadekoBot.Client.UserUpdated += UsrUpdtd; - NadekoBot.Client.UserBanned += UsrBanned; - - - 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 { } - }; - } - - private async void UsrBanned(object sender, UserEventArgs e) - { - try - { - Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) - return; - await ch.SendMessage($"`User banned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); - } - catch { } - } - - public Func DoFunc() => async e => - { - Channel ch; - if (!logs.TryRemove(e.Server, out ch)) - { - logs.TryAdd(e.Server, e.Channel); - await e.Channel.SendMessage($"**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**").ConfigureAwait(false); - return; - } - - await e.Channel.SendMessage($"**NO LONGER LOGGING IN {ch.Mention} CHANNEL**").ConfigureAwait(false); - }; - - private async void MsgRecivd(object sender, MessageEventArgs e) - { - try - { - if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) - return; - Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) - return; - await ch.SendMessage($"`Type:` **Message received** `Time:` **{DateTime.Now}** `Channel:` **{e.Channel.Name}**\n`{e.User}:` {e.Message.Text}").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; - Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) - return; - await ch.SendMessage($"`Type:` **Message deleted** `Time:` **{DateTime.Now}** `Channel:` **{e.Channel.Name}**\n`{e.User}:` {e.Message.Text}").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; - Channel ch; - if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) - return; - await ch.SendMessage($"`Type:` **Message updated** `Time:` **{DateTime.Now}** `Channel:` **{e.Channel.Name}**\n**BEFORE**: `{e.User}:` {e.Before.Text}\n---------------\n**AFTER**: `{e.User}:` {e.After.Text}").ConfigureAwait(false); - } - catch { } - } - private async void UsrUpdtd(object sender, UserUpdatedEventArgs e) - { - try - { - Channel ch; - if (loggingPresences.TryGetValue(e.Server, out ch)) - if (e.Before.Status != e.After.Status) - { - await ch.SendMessage($"**{e.Before.Name}** is now **{e.After.Status}**.").ConfigureAwait(false); - } - } - catch { } - - try - { - if (e.Before.VoiceChannel != null && voiceChannelLog.ContainsKey(e.Before.VoiceChannel)) - { - if (e.After.VoiceChannel != e.Before.VoiceChannel) - await voiceChannelLog[e.Before.VoiceChannel].SendMessage($"🎼`{e.Before.Name} has left the` {e.Before.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false); - } - if (e.After.VoiceChannel != null && voiceChannelLog.ContainsKey(e.After.VoiceChannel)) - { - if (e.After.VoiceChannel != e.Before.VoiceChannel) - await voiceChannelLog[e.After.VoiceChannel].SendMessage($"🎼`{e.After.Name} has joined the`{e.After.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false); - } - } - catch { } - - try - { - Channel ch; - if (!logs.TryGetValue(e.Server, out ch)) - return; - string str = $"`Type:` **User updated** `Time:` **{DateTime.Now}** `User:` **{e.Before.Name}**\n"; - if (e.Before.Name != e.After.Name) - str += $"`New name:` **{e.After.Name}**"; - else if (e.Before.AvatarUrl != e.After.AvatarUrl) - str += $"`New Avatar:` {e.After.AvatarUrl}"; - 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.") - .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. **Owner Only!**") - .AddCheck(SimpleCheckers.OwnerOnly()) - .AddCheck(SimpleCheckers.ManageServer()) - .Do(DoFunc()); - - cgb.CreateCommand(Module.Prefix + "userpresence") - .Description("Starts logging to this channel when someone from the server goes online/offline/idle. **Owner Only!**") - .AddCheck(SimpleCheckers.OwnerOnly()) - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - Channel ch; - if (!loggingPresences.TryRemove(e.Server, out ch)) - { - loggingPresences.TryAdd(e.Server, e.Channel); - await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); - return; - } - - 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. **Owner Only!**") - .Parameter("all", ParameterType.Optional) - .AddCheck(SimpleCheckers.OwnerOnly()) - .AddCheck(SimpleCheckers.ManageServer()) - .Do(async e => - { - - if (e.GetArg("all")?.ToLower() == "all") - { - foreach (var voiceChannel in e.Server.VoiceChannels) - { - voiceChannelLog.TryAdd(voiceChannel, e.Channel); - } - 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; - } - Channel throwaway; - if (!voiceChannelLog.TryRemove(e.User.VoiceChannel, out throwaway)) - { - voiceChannelLog.TryAdd(e.User.VoiceChannel, e.Channel); - 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/Help/Commands/HelpCommand.cs b/NadekoBot/Modules/Help/Commands/HelpCommand.cs deleted file mode 100644 index 8aedcea96..000000000 --- a/NadekoBot/Modules/Help/Commands/HelpCommand.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Discord.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules; -using NadekoBot.Modules.Permissions.Classes; -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace NadekoBot.Classes.Help.Commands -{ - internal class HelpCommand : DiscordCommand - { - public Func DoFunc() => async e => - { - #region OldHelp - /* - string helpstr = "**COMMANDS DO NOT WORK IN PERSONAL MESSAGES**\nOfficial repo: **github.com/Kwoth/NadekoBot/**"; - - string lastCategory = ""; - foreach (var com in client.GetService().AllCommands) - { - if (com.Category != lastCategory) - { - helpstr += "\n`----`**`" + com.Category + "`**`----`\n"; - lastCategory = com.Category; - } - helpstr += PrintCommandHelp(com); - } - helpstr += "\nBot Creator's server: https://discord.gg/0ehQwTK2RBhxEi0X"; - helpstr = helpstr.Replace(NadekoBot.botMention, "@BotName"); - while (helpstr.Length > 2000) - { - var curstr = helpstr.Substring(0, 2000); - await e.User.Send(curstr.Substring(0, curstr.LastIndexOf("\n") + 1)).ConfigureAwait(false); - helpstr = curstr.Substring(curstr.LastIndexOf("\n") + 1) + helpstr.Substring(2000); - await Task.Delay(200).ConfigureAwait(false); - } - */ - #endregion OldHelp - - if (string.IsNullOrWhiteSpace(e.GetArg("command"))) - { - await e.User.Send(HelpString).ConfigureAwait(false); - return; - } - await Task.Run(async () => - { - var comToFind = e.GetArg("command"); - - var com = NadekoBot.Client.GetService().AllCommands - .FirstOrDefault(c => c.Text.ToLower().Equals(comToFind)); - if (com != null) - await e.Channel.SendMessage($"`Help for '{com.Text}':` **{com.Description}**").ConfigureAwait(false); - }).ConfigureAwait(false); - }; - public static string HelpString => (NadekoBot.IsBot - ? $"To add me to your server, use this link** -> \n" - : $"To invite me to your server, just send me an invite link here.") + - $"You can use `{NadekoBot.Config.CommandPrefixes.Help}modules` command to see a list of all modules.\n" + - $"You can use `{NadekoBot.Config.CommandPrefixes.Help}commands ModuleName`" + - $" (for example `{NadekoBot.Config.CommandPrefixes.Help}commands Administration`) to see a list of all of the commands in that module.\n" + - $"For a specific command help, use `{NadekoBot.Config.CommandPrefixes.Help}h \"Command name\"` (for example `-h \"!m q\"`)\n" + - "**LIST OF COMMANDS CAN BE FOUND ON THIS LINK**\n\n "; - - public static string DMHelpString => NadekoBot.Config.DMHelpString; - - public Action DoGitFunc() => e => - { - string helpstr = -$@"######For more information and how to setup your own NadekoBot, go to: **http://github.com/Kwoth/NadekoBot/** -######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` - -#NadekoBot List Of Commands -Version: `{NadekoStats.Instance.BotVersion}`"; - - - string lastCategory = ""; - foreach (var com in NadekoBot.Client.GetService().AllCommands) - { - if (com.Category != lastCategory) - { - helpstr += "\n### " + com.Category + " \n"; - helpstr += "Command and aliases | Description | Usage\n"; - helpstr += "----------------|--------------|-------\n"; - lastCategory = com.Category; - } - helpstr += PrintCommandHelp(com); - } - helpstr = helpstr.Replace(NadekoBot.BotMention, "@BotName"); - helpstr = helpstr.Replace("\n**Usage**:", " | ").Replace("**Usage**:", " | ").Replace("**Description:**", " | ").Replace("\n|", " | \n"); -#if DEBUG - File.WriteAllText("../../../commandlist.md", helpstr); -#else - File.WriteAllText("commandlist.md", helpstr); -#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.\n**Usage**: '-h !m q' or just '-h' ") - .Parameter("command", ParameterType.Unparsed) - .Do(DoFunc()); - cgb.CreateCommand(Module.Prefix + "hgit") - .Description("Generates the commandlist.md file. **Owner Only!**") - .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.") - .Do(async e => - await e.Channel.SendMessage( -@"**FULL README**: - -**GUIDE ONLY**: - -**LIST OF COMMANDS**: ").ConfigureAwait(false)); - - cgb.CreateCommand(Module.Prefix + "donate") - .Alias("~donate") - .Description("Instructions for helping the project!") - .Do(async e => - { - await e.Channel.SendMessage( -$@"I've created a **paypal** email for nadeko, so if you wish to support the project, you can send your donations to `nadekodiscordbot@gmail.com` -Don't forget to leave your discord name or id in the message, so that I can reward people who help out. -You can join nadekobot server by typing {Module.Prefix}h and you will get an invite in a private message. - -*If you want to support in some other way or on a different platform, please message me*" - ).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/_Models/JSONModels/Configuration.cs b/NadekoBot/_Models/JSONModels/Configuration.cs deleted file mode 100644 index 91d866ed6..000000000 --- a/NadekoBot/_Models/JSONModels/Configuration.cs +++ /dev/null @@ -1,137 +0,0 @@ -using Discord; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.IO; - -namespace NadekoBot.Classes.JSONModels -{ - public class Configuration - { - public bool DontJoinServers { get; set; } = false; - public bool ForwardMessages { get; set; } = true; - public bool IsRotatingStatus { get; set; } = false; - - [JsonIgnore] - public List Quotes { get; set; } = new List(); - - [JsonIgnore] - public List PokemonTypes { get; set; } = new List(); - - - 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 - }; - - - 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[] DisguiseResponses { get; set; } = { - "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" - }; - - public string[] CryResponses { get; set; } = { - "http://i.imgur.com/Xg3i1Qy.gif", - "http://i.imgur.com/3K8DRrU.gif", - "http://i.imgur.com/k58BcAv.gif", - "http://i.imgur.com/I2fLXwo.gif" - }; - - public string[] PatResponses { get; set; } = { - "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 ", - }; - - public string CurrencySign { get; set; } = "🌸"; - public string CurrencyName { get; set; } = "NadekoFlower"; - public string DMHelpString { get; set; } = "Type `-h` for help."; - } - - 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; } = "!m"; - 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 static class ConfigHandler - { - private static readonly object configLock = new object(); - public static void SaveConfig() - { - lock (configLock) - { - File.WriteAllText("data/config.json", JsonConvert.SerializeObject(NadekoBot.Config, Formatting.Indented)); - } - } - - 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/_JSONModels.cs b/NadekoBot/_Models/JSONModels/_JSONModels.cs deleted file mode 100644 index b8f9b086c..000000000 --- a/NadekoBot/_Models/JSONModels/_JSONModels.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ReSharper disable InconsistentNaming -namespace NadekoBot.Classes.JSONModels -{ - public class Credentials - { - public string Username = "myemail@email.com"; - public string Password = "xxxxxxx"; - public string Token = ""; - public ulong BotId = 1231231231231; - public string GoogleAPIKey = ""; - public ulong[] OwnerIds = { 123123123123, 5675675679845 }; - public string TrelloAppKey = ""; - public string SoundCloudClientID = ""; - public string MashapeKey = ""; - public string LOLAPIKey = ""; - public string CarbonKey = ""; - } -} \ 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 8af5fe023..000000000 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 d39e3fc17..000000000 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 ac6cbce15..000000000 Binary files a/NadekoBot/bin/Debug/WebSocket4Net.dll and /dev/null differ diff --git a/NadekoBot/bin/Debug/data/avatar.png b/NadekoBot/bin/Debug/data/avatar.png deleted file mode 100644 index 37862c4bc..000000000 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 68f48c456..000000000 --- a/NadekoBot/bin/Debug/data/config_example.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "DontJoinServers": false, - "ForwardMessages": true, - "IsRotatingStatus": false, - "RotatingStatuses": [], - "CommandPrefixes": { - "Administration": ".", - "Searches": "~", - "NSFW": "~", - "Conversations": "<@{0}>", - "ClashOfClans": ",", - "Help": "-", - "Music": "!m", - "Trello": "trello", - "Games": ">", - "Gambling": "$", - "Permissions": ";", - "Programming": "%", - "Pokemon": ">" - }, - "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" - ], - "DisguiseResponses": [ - "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" - ], - "CryResponses": [ - "http://i.imgur.com/Xg3i1Qy.gif", - "http://i.imgur.com/3K8DRrU.gif", - "http://i.imgur.com/k58BcAv.gif", - "http://i.imgur.com/I2fLXwo.gif" - ], - "PatResponses": [ - "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 " - ], - "CurrencySign": "🌸", - "CurrencyName": "NadekoFlower", - "DMHelpString": "Type `-h` for help." -} \ No newline at end of file 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 828509b36..000000000 Binary files a/NadekoBot/bin/Debug/data/lol/champions/Thumbs.db 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 edce7e326..000000000 Binary files a/NadekoBot/bin/Debug/data/lol/items/Thumbs.db and /dev/null differ diff --git a/NadekoBot/bin/Debug/libsodium.dll b/NadekoBot/bin/Debug/libsodium.dll deleted file mode 100644 index a9ab5078e..000000000 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 a9eec802c..000000000 Binary files a/NadekoBot/bin/Debug/opus.dll and /dev/null differ diff --git a/NadekoBot/bin/Debug/sqlite3.dll b/NadekoBot/bin/Debug/sqlite3.dll deleted file mode 100644 index 59e84a1cf..000000000 Binary files a/NadekoBot/bin/Debug/sqlite3.dll and /dev/null differ diff --git a/NadekoBot/lib/ScaredFingers.UnitsConversion.dll b/NadekoBot/lib/ScaredFingers.UnitsConversion.dll deleted file mode 100644 index 8b1218117..000000000 Binary files a/NadekoBot/lib/ScaredFingers.UnitsConversion.dll and /dev/null differ diff --git a/NadekoBot/lib/ScaredFingers.UnitsConversion.pdb b/NadekoBot/lib/ScaredFingers.UnitsConversion.pdb deleted file mode 100644 index f63024ad3..000000000 Binary files a/NadekoBot/lib/ScaredFingers.UnitsConversion.pdb and /dev/null differ diff --git a/NadekoBot/packages.config b/NadekoBot/packages.config deleted file mode 100644 index 419ac0b02..000000000 --- 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 13a0bb6c8..000000000 Binary files a/NadekoBot/resources/images/cards/Thumbs.db and /dev/null differ diff --git a/README.md b/README.md index f68895ed6..adc3e9378 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,10 @@ ![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true) -# NadekoBot +# WizBot - Outdated Version -### [Click here to invite nadeko to your server](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) -[**click here for a list of commands**](https://github.com/Kwoth/NadekoBot/blob/master/commandlist.md) -Nadeko Discord chatbot written in C# using Discord.net library. -You might want to join my discord server where i can provide help etc. https://discord.gg/0ehQwTK2RBjAxzEY +[**click here for a list of commands**](https://github.com/Wizkiller96/WizBot-Updated/blob/master/commandlist.md) +WizBot Discord chatbot written in C# using Discord.net library. -##This section will guide you through how to setup NadekoBot -#### 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 comprehensive [GUIDE](https://github.com/Kwoth/NadekoBot/blob/master/ComprehensiveGuide.md) +##This section will guide you through how to setup WizBot 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. @@ -16,9 +13,8 @@ When you clone the project, make sure to run `git submodule init` and `git submo **This is how the credentials.json should look like:** ```json { - "Username":"bot_email", "BotId": 123123123123, - "Password":"bot_password", + "Token":"Bot.Token", "GoogleAPIKey":"google_api_key", "OwnerIds":[123123123123, 123123123123], "TrelloAppKey": "your_trello_app_key (optional)", @@ -52,17 +48,16 @@ Next to your exe you must also have a data folder in which there is config.json "UserBlacklist": [] } ``` +- http://discord.kongslien.net/guide.html <- to make a bot account and get the `Token` - BotId and OwnerIds are **NOT** names of the owner and the bot. If you do not know the id of your bot, put 2 random numbers in those fields, run the bot and do `.uid @MyBotName` - that will give you your bot\_id, do the same for yourself `.uid @MyName` and copy the numbers in their respective fields. -- For google api key, you need to enable URL shortner, Youtube video search **and custom search** in the [dev console](https://console.developers.google.com/). +- For google api key, you need to enable URL shortner, Youtube data API and Custom search in the [dev console](https://console.developers.google.com/). - For the Soundcloud Api key you need a Soundcloud account. You need to create a new app on http://soundcloud.com/you/apps/new and after that go here http://soundcloud.com/you/apps click on the name of your created your app and copy the Client ID. Paste it into credentials.json. - For Mashape Api Key you need to create an account on their api marketplace here https://market.mashape.com/. After that you need to go to market.mashape.com/YOURNAMEHERE/applications/default-application and press GET THE KEYS in the right top corner copy paste it into your credentials.json and you are ready to race! - If you want to have music, you need to download FFMPEG from this link http://ffmpeg.zeranoe.com/builds/ (static build version) and add ffmpeg/bin folder to your PATH environment variable. You do that by opening explorer -> right click 'This PC' -> properties -> advanced system settings -> In the top part, there is a PATH field, add `;` to the end and then your ffmpeg install location /bin (for example ;C:\ffmpeg-5.6.7\bin) and save. Open command prompt and type ffmpeg to see if you added it correctly. If it says "command not found" then you made a mistake somewhere. There are a lot of guides on the internet on how to add stuff to your PATH, check them out if you are stuck. - **IF YOU HAVE BEEN USING THIS BOT BEFORE AND YOU HAVE DATA FROM PARSE THAT YOU WANT TO KEEP** you should export your parse data and extract it inside /data/parsedata in your bot's folder. Next time you start the bot, type `.parsetosql` and the bot will fill your local sqlite db with data from those .json files. - -**Nothing was buffered music error?** make sure to follow the guide on google api key and ffmpeg [here](https://www.youtube.com/watch?v=x7v02MXNLeI) Enjoy ##List of commands -[**click here for a list of commands**](https://github.com/Kwoth/NadekoBot/blob/master/commandlist.md) +[**click here for a list of commands**](https://github.com/Wizkiller96/WizBot-Updated/blob/master/commandlist.md) diff --git a/NadekoBot/SQLite.cs b/Tests/SQLite.cs similarity index 100% rename from NadekoBot/SQLite.cs rename to Tests/SQLite.cs diff --git a/NadekoBot/SQLiteAsync.cs b/Tests/SQLiteAsync.cs similarity index 100% rename from NadekoBot/SQLiteAsync.cs rename to Tests/SQLiteAsync.cs diff --git a/Tests/TestCards.cs b/Tests/TestCards.cs index f346fc372..a17fcc948 100644 --- a/Tests/TestCards.cs +++ b/Tests/TestCards.cs @@ -1,6 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; -using static NadekoBot.Modules.Gambling.Helpers.Cards; +using static WizBot.Modules.Gambling.Helpers.Cards; namespace Tests { diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 0ddc6cc5b..101bba320 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -36,7 +36,89 @@ 4 + + ..\packages\Discord.Net.0.9.3\lib\net45\Discord.Net.dll + True + + + ..\packages\Discord.Net.Audio.0.9.3\lib\net45\Discord.Net.Audio.dll + True + + + ..\packages\Discord.Net.Commands.0.9.3\lib\net45\Discord.Net.Commands.dll + True + + + ..\packages\Discord.Net.Modules.0.9.3\lib\net45\Discord.Net.Modules.dll + True + + + ..\packages\VideoLibrary.1.3.3\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\libvideo.dll + True + + + ..\packages\Manatee.Json.4.0.1\lib\net45\Manatee.Json.dll + True + + + ..\packages\Manatee.StateMachine.1.1.2\lib\net45\Manatee.StateMachine.dll + True + + + ..\packages\Manatee.Trello.1.10.0\lib\net45\Manatee.Trello.dll + True + + + ..\packages\Manatee.Trello.ManateeJson.1.5.0\lib\net45\Manatee.Trello.ManateeJson.dll + True + + + ..\packages\Manatee.Trello.WebApi.1.0.4\lib\net45\Manatee.Trello.WebApi.dll + True + + + + ..\packages\Newtonsoft.Json.8.0.4-beta1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll + True + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll + True + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll + True + + + ..\packages\taglib.2.1.0.0\lib\policy.2.0.taglib-sharp.dll + True + + + ..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll + True + + + + ..\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 + + + ..\packages\taglib.2.1.0.0\lib\taglib-sharp.dll + True + + + ..\packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll + True + @@ -51,13 +133,19 @@ + + - + + + + + {27a886f5-cdda-4f4a-81ee-6dafcce9de46} - NadekoBot + WizBot diff --git a/Tests/app.config b/Tests/app.config new file mode 100644 index 000000000..de5386a47 --- /dev/null +++ b/Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/packages.config b/Tests/packages.config new file mode 100644 index 000000000..6b0ad71e0 --- /dev/null +++ b/Tests/packages.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NadekoBot.sln b/WizBot.sln similarity index 82% rename from NadekoBot.sln rename to WizBot.sln index 5e8a3b963..746913d3b 100644 --- a/NadekoBot.sln +++ b/WizBot.sln @@ -1,9 +1,9 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25008.0 +VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NadekoBot", "NadekoBot\NadekoBot.csproj", "{27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WizBot", "WizBot\WizBot.csproj", "{27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}" 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}" EndProject @@ -26,7 +26,7 @@ Global Debug|Any CPU = Debug|Any CPU FullDebug|Any CPU = FullDebug|Any CPU Release|Any CPU = Release|Any CPU - TestResponses|Any CPU = TestResponses|Any CPU + WizBotRelease|Any CPU = WizBotRelease|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -35,48 +35,48 @@ Global {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {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}.TestResponses|Any CPU.ActiveCfg = PRIVATE|Any CPU - {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.TestResponses|Any CPU.Build.0 = PRIVATE|Any CPU + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.WizBotRelease|Any CPU.ActiveCfg = WizBotRelease|Any CPU + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46}.WizBotRelease|Any CPU.Build.0 = WizBotRelease|Any CPU {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}.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}.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}.TestResponses|Any CPU.ActiveCfg = Release|Any CPU - {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.TestResponses|Any CPU.Build.0 = Release|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.WizBotRelease|Any CPU.ActiveCfg = Release|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.WizBotRelease|Any CPU.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}.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}.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}.TestResponses|Any CPU.ActiveCfg = TestResponses|Any CPU - {8D71A857-879A-4A10-859E-5FF824ED6688}.TestResponses|Any CPU.Build.0 = TestResponses|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.WizBotRelease|Any CPU.ActiveCfg = Release|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.WizBotRelease|Any CPU.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}.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}.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}.TestResponses|Any CPU.ActiveCfg = Release|Any CPU - {3091164F-66AE-4543-A63D-167C1116241D}.TestResponses|Any CPU.Build.0 = Release|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.WizBotRelease|Any CPU.ActiveCfg = Release|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.WizBotRelease|Any CPU.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}.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}.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}.TestResponses|Any CPU.ActiveCfg = Release|Any CPU - {1B5603B4-6F8F-4289-B945-7BAAE523D740}.TestResponses|Any CPU.Build.0 = Release|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.WizBotRelease|Any CPU.ActiveCfg = Release|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.WizBotRelease|Any CPU.Build.0 = Release|Any CPU {45B2545D-C612-4919-B34C-D65EA1371C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {45B2545D-C612-4919-B34C-D65EA1371C51}.Debug|Any CPU.Build.0 = Debug|Any CPU {45B2545D-C612-4919-B34C-D65EA1371C51}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU {45B2545D-C612-4919-B34C-D65EA1371C51}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {45B2545D-C612-4919-B34C-D65EA1371C51}.Release|Any CPU.ActiveCfg = Release|Any CPU {45B2545D-C612-4919-B34C-D65EA1371C51}.Release|Any CPU.Build.0 = Release|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.TestResponses|Any CPU.ActiveCfg = Release|Any CPU - {45B2545D-C612-4919-B34C-D65EA1371C51}.TestResponses|Any CPU.Build.0 = Release|Any CPU + {45B2545D-C612-4919-B34C-D65EA1371C51}.WizBotRelease|Any CPU.ActiveCfg = Release|Any CPU + {45B2545D-C612-4919-B34C-D65EA1371C51}.WizBotRelease|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NadekoBot/App.config b/WizBot/App.config similarity index 100% rename from NadekoBot/App.config rename to WizBot/App.config diff --git a/NadekoBot/Classes/BombermanGame.cs b/WizBot/Classes/BombermanGame.cs similarity index 100% rename from NadekoBot/Classes/BombermanGame.cs rename to WizBot/Classes/BombermanGame.cs diff --git a/NadekoBot/Classes/DBHandler.cs b/WizBot/Classes/DBHandler.cs similarity index 82% rename from NadekoBot/Classes/DBHandler.cs rename to WizBot/Classes/DBHandler.cs index b6b85c9cf..d51f46d07 100644 --- a/NadekoBot/Classes/DBHandler.cs +++ b/WizBot/Classes/DBHandler.cs @@ -1,17 +1,17 @@ -using NadekoBot.DataModels; +using WizBot.DataModels; using SQLite; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -namespace NadekoBot.Classes +namespace WizBot.Classes { internal class DbHandler { public static DbHandler Instance { get; } = new DbHandler(); - private string FilePath { get; } = "data/nadekobot.sqlite"; + private string FilePath { get; } = "data/WizBot.sqlite"; static DbHandler() { } public DbHandler() @@ -158,9 +158,43 @@ internal CurrencyState GetStateByUserId(long id) return conn.Table().Where(p).ToList().OrderBy(x => r.Next()).FirstOrDefault(); } } + /// + /// + /// + /// Page number (0+) + /// + internal List GetPlaylistData(int num) + { + using (var conn = new SQLiteConnection(FilePath)) + { + return conn.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) + { + using (var conn = new SQLiteConnection(FilePath)) + { + return conn.Table().Take(n).ToList().OrderBy(cs => -cs.Value); + } + } } } +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 static string TransactionTriggerQuery = @" diff --git a/NadekoBot/Classes/Extensions.cs b/WizBot/Classes/Extensions.cs similarity index 90% rename from NadekoBot/Classes/Extensions.cs rename to WizBot/Classes/Extensions.cs index fea6b61ac..c48dece38 100644 --- a/NadekoBot/Classes/Extensions.cs +++ b/WizBot/Classes/Extensions.cs @@ -1,15 +1,16 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; +using WizBot.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.Threading.Tasks; -namespace NadekoBot.Extensions +namespace WizBot.Extensions { public static class Extensions { @@ -138,14 +139,21 @@ public static async Task Reply(this MessageEventArgs e, string message) /// public static void 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[1]; - do provider.GetBytes(box); - while (!(box[0] < n * (byte.MaxValue / n))); - var k = (box[0] % n); + 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]; @@ -159,7 +167,18 @@ public static void Shuffle(this IList list) /// /// /// - public static async Task ShortenUrl(this string str) => await SearchHelper.ShortenUrl(str).ConfigureAwait(false); + 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); + } + } /// /// Gets the program runtime @@ -284,5 +303,7 @@ public static Bitmap Merge(this IEnumerable images, int reverseScaleFacto /// 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("@", "ම"); } } diff --git a/NadekoBot/Classes/FlowersHandler.cs b/WizBot/Classes/FlowersHandler.cs similarity index 88% rename from NadekoBot/Classes/FlowersHandler.cs rename to WizBot/Classes/FlowersHandler.cs index f05ce4746..229623f5a 100644 --- a/NadekoBot/Classes/FlowersHandler.cs +++ b/WizBot/Classes/FlowersHandler.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace NadekoBot.Classes +namespace WizBot.Classes { internal static class FlowersHandler { @@ -21,11 +21,8 @@ await Task.Run(() => if (silent) return; - var flows = ""; - for (var i = 0; i < amount; i++) - { - flows += NadekoBot.Config.CurrencySign; - } + var flows = amount + " " + WizBot.Config.CurrencySign; + await u.SendMessage("👑Congratulations!👑\nYou received: " + flows).ConfigureAwait(false); } diff --git a/NadekoBot/Classes/IncidentsHandler.cs b/WizBot/Classes/IncidentsHandler.cs similarity index 94% rename from NadekoBot/Classes/IncidentsHandler.cs rename to WizBot/Classes/IncidentsHandler.cs index 0791fa6cf..e289ddbdc 100644 --- a/NadekoBot/Classes/IncidentsHandler.cs +++ b/WizBot/Classes/IncidentsHandler.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace NadekoBot.Classes { +namespace WizBot.Classes { internal static class IncidentsHandler { public static void Add(ulong serverId, string text) { Directory.CreateDirectory("data/incidents"); diff --git a/NadekoBot/Classes/SearchHelper.cs b/WizBot/Classes/SearchHelper.cs similarity index 69% rename from NadekoBot/Classes/SearchHelper.cs rename to WizBot/Classes/SearchHelper.cs index adf094a15..bd9b979b7 100644 --- a/NadekoBot/Classes/SearchHelper.cs +++ b/WizBot/Classes/SearchHelper.cs @@ -1,5 +1,5 @@ -using NadekoBot.Classes.JSONModels; -using NadekoBot.Extensions; +using WizBot.Classes.JSONModels; +using WizBot.Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -13,7 +13,7 @@ using System.Threading.Tasks; using System.Xml.Linq; -namespace NadekoBot.Classes +namespace WizBot.Classes { public enum RequestHttpMethod { @@ -25,13 +25,17 @@ public static class SearchHelper { private static DateTime lastRefreshed = DateTime.MinValue; private static string token { get; set; } = ""; + private static readonly HttpClient httpClient = new HttpClient(); public static async Task GetResponseStreamAsync(string url, IEnumerable> headers = null, RequestHttpMethod method = RequestHttpMethod.Get) { if (string.IsNullOrWhiteSpace(url)) throw new ArgumentNullException(nameof(url)); - var httpClient = new HttpClient(); + //if its a post or there are no headers, use static httpclient + // if there are headers and it's get, it's not threadsafe + var cl = headers == null || method == RequestHttpMethod.Post ? httpClient : new HttpClient(); + cl.DefaultRequestHeaders.Clear(); switch (method) { case RequestHttpMethod.Get: @@ -39,17 +43,17 @@ public static async Task GetResponseStreamAsync(string url, { foreach (var header in headers) { - httpClient.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value); + cl.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value); } } - return await httpClient.GetStreamAsync(url).ConfigureAwait(false); + return await cl.GetStreamAsync(url).ConfigureAwait(false); case RequestHttpMethod.Post: FormUrlEncodedContent formContent = null; if (headers != null) { formContent = new FormUrlEncodedContent(headers); } - var message = await httpClient.PostAsync(url, formContent).ConfigureAwait(false); + 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."); @@ -141,7 +145,7 @@ public static async Task ValidateQuery(Discord.Channel ch, string query) public static async Task FindYoutubeUrlByKeywords(string keywords) { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) + if (string.IsNullOrWhiteSpace(WizBot.Creds.GoogleAPIKey)) throw new InvalidCredentialException("Google API Key is missing."); if (string.IsNullOrWhiteSpace(keywords)) throw new ArgumentNullException(nameof(keywords), "Query not specified."); @@ -152,51 +156,78 @@ public static async Task FindYoutubeUrlByKeywords(string keywords) var match = new Regex("(?:youtu\\.be\\/|v=)(?[\\da-zA-Z\\-_]*)").Match(keywords); if (match.Length > 1) { - return $"http://www.youtube.com?v={match.Groups["id"].Value}"; + return $"https://www.youtube.com/watch?v={match.Groups["id"].Value}"; } 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); - dynamic obj = JObject.Parse(response); - return "http://www.youtube.com/watch?v=" + obj.items[0].id.videoId.ToString(); + $"&key={WizBot.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 GetPlaylistIdByKeyword(string query) { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) + if (string.IsNullOrWhiteSpace(WizBot.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}"; + $"&key={WizBot.Creds.GoogleAPIKey}"; var response = await GetResponseStringAsync(link).ConfigureAwait(false); - dynamic obj = JObject.Parse(response); + var data = JsonConvert.DeserializeObject(response); + JObject obj = JObject.Parse(response); - return obj.items[0].id.playlistId.ToString(); + return data.items.Length > 0 ? data.items[0].id.playlistId.ToString() : null; } - public static async Task> GetVideoIDs(string playlist, int number = 30) + public static async Task> GetVideoIDs(string playlist, int number = 50) { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.GoogleAPIKey)) + if (string.IsNullOrWhiteSpace(WizBot.Creds.GoogleAPIKey)) { throw new ArgumentNullException(nameof(playlist)); } - if (number < 1 || number > 100) + if (number < 1) throw new ArgumentOutOfRangeException(); - var link = - $"https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails" + - $"&maxResults={30}" + - $"&playlistId={playlist}" + - $"&key={NadekoBot.Creds.GoogleAPIKey}"; - var response = await GetResponseStringAsync(link).ConfigureAwait(false); - var obj = await Task.Run(() => JObject.Parse(response)).ConfigureAwait(false); + string nextPageToken = null; - return obj["items"].Select(item => "http://www.youtube.com/watch?v=" + item["contentDetails"]["videoId"]); + 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={WizBot.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; } @@ -215,18 +246,24 @@ public static async Task GetDanbooruImageLink(string tag) 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).ConfigureAwait(false); + $"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) - throw new FileNotFoundException(); + return null; var rng = new Random(); var match = matches[rng.Next(0, matches.Count)]; return matches[rng.Next(0, matches.Count)].Groups["url"].Value; @@ -240,7 +277,7 @@ public static async Task GetSafebooruImageLink(string tag) var webpage = await GetResponseStringAsync(url).ConfigureAwait(false); var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); if (matches.Count == 0) - throw new FileNotFoundException(); + return null; var match = matches[rng.Next(0, matches.Count)]; return matches[rng.Next(0, matches.Count)].Groups["url"].Value; } @@ -253,7 +290,7 @@ public static async Task GetRule34ImageLink(string tag) var webpage = await GetResponseStringAsync(url).ConfigureAwait(false); var matches = Regex.Matches(webpage, "file_url=\"(?.*?)\""); if (matches.Count == 0) - throw new FileNotFoundException(); + return null; var match = matches[rng.Next(0, matches.Count)]; return "http:" + matches[rng.Next(0, matches.Count)].Groups["url"].Value; } @@ -263,24 +300,31 @@ internal static async Task GetE621ImageLink(string tags) { try { - XDocument doc = await Task.Run(() => XDocument.Load(" http://e621.net/post/index.xml?tags=" + Uri.EscapeUriString(tags) + "%20order:random&limit=1")); - int id = Convert.ToInt32(doc.Root.Element("post").Element("id").Value); - return (doc.Root.Element("post").Element("file_url").Value); + 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) + 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; + if (string.IsNullOrWhiteSpace(WizBot.Creds.GoogleAPIKey)) return url; try { var httpWebRequest = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/urlshortener/v1/url?key=" + - NadekoBot.Creds.GoogleAPIKey); + WizBot.Creds.GoogleAPIKey); httpWebRequest.ContentType = "application/json"; httpWebRequest.Method = "POST"; @@ -307,4 +351,4 @@ public static async Task ShortenUrl(string url) } } } -} +} \ No newline at end of file diff --git a/NadekoBot/Classes/ServerSpecificConfig.cs b/WizBot/Classes/ServerSpecificConfig.cs similarity index 92% rename from NadekoBot/Classes/ServerSpecificConfig.cs rename to WizBot/Classes/ServerSpecificConfig.cs index a7615b561..e68c76f36 100644 --- a/NadekoBot/Classes/ServerSpecificConfig.cs +++ b/WizBot/Classes/ServerSpecificConfig.cs @@ -7,7 +7,7 @@ using System.IO; using System.Runtime.CompilerServices; -namespace NadekoBot.Classes +namespace WizBot.Classes { internal class SpecificConfigurations { @@ -95,6 +95,19 @@ public ObservableCollection ListOfSelfAssignableRoles { } } + [JsonIgnore] + private ulong autoAssignedRole = 0; + public ulong AutoAssignedRole + { + get { return autoAssignedRole; } + set + { + autoAssignedRole = value; + if (!SpecificConfigurations.Instantiated) return; + OnPropertyChanged(); + } + } + [JsonIgnore] private ObservableCollection observingStreams; public ObservableCollection ObservingStreams { @@ -120,7 +133,7 @@ public ServerSpecificConfig() private void OnPropertyChanged([CallerMemberName] string propertyName = null) { - Console.WriteLine("property changed"); + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } diff --git a/NadekoBot/Classes/NadekoStats.cs b/WizBot/Classes/WizStats.cs similarity index 75% rename from NadekoBot/Classes/NadekoStats.cs rename to WizBot/Classes/WizStats.cs index 69dcdf703..07c8f5ca3 100644 --- a/NadekoBot/Classes/NadekoStats.cs +++ b/WizBot/Classes/WizStats.cs @@ -1,8 +1,8 @@ using Discord; using Discord.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules.Administration.Commands; -using NadekoBot.Modules.Music; +using WizBot.Extensions; +using WizBot.Modules.Administration.Commands; +using WizBot.Modules.Music; using System; using System.Collections.Generic; using System.Diagnostics; @@ -12,11 +12,11 @@ using System.Threading.Tasks; using System.Timers; -namespace NadekoBot +namespace WizBot { - public class NadekoStats + public class WizStats { - public static NadekoStats Instance { get; } = new NadekoStats(); + public static WizStats Instance { get; } = new WizStats(); public string BotVersion => $"{Assembly.GetExecutingAssembly().GetName().Name} v{Assembly.GetExecutingAssembly().GetName().Version}"; @@ -31,11 +31,14 @@ public class NadekoStats private readonly Timer commandLogTimer = new Timer() { Interval = 10000 }; private readonly Timer carbonStatusTimer = new Timer() { Interval = 3600000 }; - static NadekoStats() { } + private static ulong messageCounter = 0; + public static ulong MessageCounter => messageCounter; - private NadekoStats() + static WizStats() { } + + private WizStats() { - var commandService = NadekoBot.Client.GetService(); + var commandService = WizBot.Client.GetService(); statsStopwatch.Start(); commandService.CommandExecuted += StatsCollector_RanCommand; @@ -44,13 +47,15 @@ private NadekoStats() commandLogTimer.Start(); - ServerCount = NadekoBot.Client.Servers.Count(); - var channels = NadekoBot.Client.Servers.SelectMany(s => s.AllChannels); + ServerCount = WizBot.Client.Servers.Count(); + var channels = WizBot.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.JoinedServer += (s, e) => + WizBot.Client.MessageReceived += (s, e) => messageCounter++; + + WizBot.Client.JoinedServer += (s, e) => { try { @@ -61,7 +66,7 @@ private NadekoStats() } catch { } }; - NadekoBot.Client.LeftServer += (s, e) => + WizBot.Client.LeftServer += (s, e) => { try { @@ -72,7 +77,7 @@ private NadekoStats() } catch { } }; - NadekoBot.Client.ChannelCreated += (s, e) => + WizBot.Client.ChannelCreated += (s, e) => { try { @@ -85,7 +90,7 @@ private NadekoStats() } catch { } }; - NadekoBot.Client.ChannelDestroyed += (s, e) => + WizBot.Client.ChannelDestroyed += (s, e) => { try { @@ -104,13 +109,13 @@ private NadekoStats() HttpClient carbonClient = new HttpClient(); private async Task SendUpdateToCarbon() { - if (string.IsNullOrWhiteSpace(NadekoBot.Creds.CarbonKey)) + if (string.IsNullOrWhiteSpace(WizBot.Creds.CarbonKey)) return; try { using (var content = new FormUrlEncodedContent(new Dictionary { - { "servercount", NadekoBot.Client.Servers.Count().ToString() }, - { "key", NadekoBot.Creds.CarbonKey } + { "servercount", WizBot.Client.Servers.Count().ToString() }, + { "key", WizBot.Creds.CarbonKey } })) { content.Headers.Clear(); @@ -131,7 +136,7 @@ public TimeSpan GetUptime() => public string GetUptimeString() { - var time = (DateTime.Now - Process.GetCurrentProcess().StartTime); + var time = GetUptime(); return time.Days + " days, " + time.Hours + " hours, and " + time.Minutes + " minutes."; } @@ -140,21 +145,21 @@ public Task LoadStats() => { 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("`Author: Kwoth & Wizkiller96` `Library: Discord.Net`"); sb.AppendLine($"`Bot Version: {BotVersion}`"); - sb.AppendLine($"`Bot id: {NadekoBot.Client.CurrentUser.Id}`"); + sb.AppendLine($"`Bot id: {WizBot.Client.CurrentUser.Id}`"); sb.Append("`Owners' Ids:` "); - sb.AppendLine("`" + String.Join(", ", NadekoBot.Creds.OwnerIds) + "`"); + sb.AppendLine("`" + String.Join(", ", WizBot.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.AppendLine($"`Message queue size: {WizBot.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($"`Heap: {Heap(false)}`"); + sb.AppendLine($"`Messages: {messageCounter} ({messageCounter / (double)GetUptime().TotalSeconds:F2}/sec)` `Heap: {Heap(false)}`"); statsCache = sb.ToString(); }); @@ -176,11 +181,11 @@ private async Task StartCollecting() await Task.Delay(new TimeSpan(0, 30, 0)).ConfigureAwait(false); try { - var onlineUsers = await Task.Run(() => NadekoBot.Client.Servers.Sum(x => x.Users.Count())).ConfigureAwait(false); - var realOnlineUsers = await Task.Run(() => NadekoBot.Client.Servers + var onlineUsers = await Task.Run(() => WizBot.Client.Servers.Sum(x => x.Users.Count())).ConfigureAwait(false); + var realOnlineUsers = await Task.Run(() => WizBot.Client.Servers .Sum(x => x.Users.Count(u => u.Status == UserStatus.Online))) .ConfigureAwait(false); - var connectedServers = NadekoBot.Client.Servers.Count(); + var connectedServers = WizBot.Client.Servers.Count(); Classes.DbHandler.Instance.InsertData(new DataModels.Stats { @@ -209,19 +214,20 @@ await Task.Run(() => commandsRan++; Classes.DbHandler.Instance.InsertData(new DataModels.Command { - ServerId = (long)e.Server.Id, - ServerName = e.Server.Name, + ServerId = (long)(e.Server?.Id ?? 0), + ServerName = e.Server?.Name ?? "--Direct Message--", ChannelId = (long)e.Channel.Id, - ChannelName = e.Channel.Name, + 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 + catch (Exception ex) { - Console.WriteLine("Error in ran command DB write."); + Console.WriteLine("Probably unimportant error in ran command DB write."); + Console.WriteLine(ex); } }).ConfigureAwait(false); } diff --git a/NadekoBot/Modules/Administration/AdministrationModule.cs b/WizBot/Modules/Administration/AdministrationModule.cs similarity index 73% rename from NadekoBot/Modules/Administration/AdministrationModule.cs rename to WizBot/Modules/Administration/AdministrationModule.cs index 04452192f..222de5777 100644 --- a/NadekoBot/Modules/Administration/AdministrationModule.cs +++ b/WizBot/Modules/Administration/AdministrationModule.cs @@ -1,18 +1,18 @@ 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 WizBot.Classes; +using WizBot.DataModels; +using WizBot.Extensions; +using WizBot.Modules.Administration.Commands; +using WizBot.Modules.Permissions.Classes; using Newtonsoft.Json.Linq; using System; using System.IO; using System.Linq; using System.Threading.Tasks; -namespace NadekoBot.Modules.Administration +namespace WizBot.Modules.Administration { internal class AdministrationModule : DiscordModule { @@ -28,9 +28,11 @@ public AdministrationModule() commands.Add(new SelfAssignedRolesCommand(this)); commands.Add(new Remind(this)); commands.Add(new InfoCommands(this)); + commands.Add(new CustomReactionsCommands(this)); + commands.Add(new AutoAssignRole(this)); } - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Administration; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Administration; public override void Install(ModuleManager manager) { @@ -43,6 +45,17 @@ public override void Install(ModuleManager manager) commands.ForEach(cmd => cmd.Init(cgb)); + cgb.CreateCommand(Prefix + "restart") + .Description("Restarts the bot. Might not work.") + .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 + "sr").Alias(Prefix + "setrole") .Description("Sets a role for a given user.\n**Usage**: .sr @User Guest") .Parameter("user_name", ParameterType.Required) @@ -123,8 +136,68 @@ public override void Install(ModuleManager manager) } }); + cgb.CreateCommand(Prefix + "renr") + .Alias(Prefix + "renamerole") + .Description($"Renames a role. Role you are renaming must be lower than bot's highest role.\n**Usage**: `{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."); + 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."); + return; + } + await roleToEdit.Edit(r2); + await e.Channel.SendMessage("Role renamed."); + } + catch (Exception) + { + await e.Channel.SendMessage("Failed to rename role. Probably insufficient permissions."); + } + }); + + cgb.CreateCommand(Prefix + "rar").Alias(Prefix + "removeallroles") + .Description("Removes all roles from a mentioned user.\n**Usage**: .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 + "r").Alias(Prefix + "role").Alias(Prefix + "cr") - .Description("Creates a role with a given name.**Usage**: .r Awesome Role") + .Description("Creates a role with a given name.**Usage**: `.r Awesome Role`") .Parameter("role_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => @@ -147,7 +220,7 @@ public override void Install(ModuleManager manager) .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.\n**Usage**: .color Admin 255 200 100 or .color Admin ffba55") + .Description("Set a role's color to the hex or 0-255 rgb color value provided.\n**Usage**: `.color Admin 255 200 100` or `.color Admin ffba55`") .Do(async e => { if (!e.User.ServerPermissions.ManageRoles) @@ -174,10 +247,11 @@ public override void Install(ModuleManager manager) try { var rgb = args.Count() == 4; + var arg1 = e.Args[1].Replace("#", ""); - var red = Convert.ToByte(rgb ? int.Parse(e.Args[1]) : Convert.ToInt32(e.Args[1].Substring(0, 2), 16)); - var green = Convert.ToByte(rgb ? int.Parse(e.Args[2]) : Convert.ToInt32(e.Args[1].Substring(2, 2), 16)); - var blue = Convert.ToByte(rgb ? int.Parse(e.Args[3]) : Convert.ToInt32(e.Args[1].Substring(4, 2), 16)); + 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); @@ -228,7 +302,7 @@ await usr.SendMessage($"**You have been BANNED from `{e.Server.Name}` server.**\ } try { - await e.Server.Ban(usr).ConfigureAwait(false); + await e.Server.Ban(usr, 7).ConfigureAwait(false); await e.Channel.SendMessage("Banned user " + usr.Name + " Id: " + usr.Id).ConfigureAwait(false); } @@ -461,18 +535,30 @@ await usr.SendMessage($"**You have been KICKED from `{e.Server.Name}` server.**\ cgb.CreateCommand(Prefix + "st").Alias(Prefix + "settopic") .Alias(Prefix + "topic") - .Description("Sets a topic on the current channel.") + .Description($"Sets a topic on the current channel.\n**Usage**: `{Prefix}st My new topic`") .AddCheck(SimpleCheckers.ManageChannels()) .Parameter("topic", ParameterType.Unparsed) .Do(async e => { - var topic = e.GetArg("topic"); - if (string.IsNullOrWhiteSpace(topic)) - return; + 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 + "schn").Alias(Prefix + "setchannelname") + .Alias(Prefix + "topic") + .Description("Changed the name of the current channel.") + .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 + "uid").Alias(Prefix + "userid") .Description("Shows user ID.") .Parameter("user", ParameterType.Unparsed) @@ -494,17 +580,17 @@ await usr.SendMessage($"**You have been KICKED from `{e.Server.Name}` server.**\ .Do(async e => await e.Channel.SendMessage("This server's ID is " + e.Server.Id).ConfigureAwait(false)); cgb.CreateCommand(Prefix + "stats") - .Description("Shows some basic stats for Nadeko.") + .Description("Shows some basic stats for WizBot.") .Do(async e => { - await e.Channel.SendMessage(await NadekoStats.Instance.GetStats()); + await e.Channel.SendMessage(await WizStats.Instance.GetStats()); }); cgb.CreateCommand(Prefix + "dysyd") - .Description("Shows some basic stats for Nadeko.") + .Description("Shows some basic stats for WizBot.") .Do(async e => { - await e.Channel.SendMessage((await NadekoStats.Instance.GetStats()).Matrix().TrimTo(1990)).ConfigureAwait(false); + await e.Channel.SendMessage((await WizStats.Instance.GetStats()).Matrix().TrimTo(1990)).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "heap") @@ -512,54 +598,67 @@ await usr.SendMessage($"**You have been KICKED from `{e.Server.Name}` server.**\ .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - var heap = await Task.Run(() => NadekoStats.Instance.Heap()).ConfigureAwait(false); + var heap = await Task.Run(() => WizStats.Instance.Heap()).ConfigureAwait(false); await e.Channel.SendMessage($"`Heap Size:` {heap}").ConfigureAwait(false); }); + cgb.CreateCommand(Prefix + "prune") - .Parameter("num", ParameterType.Required) - .Description("Prunes a number of messages from the current channel.\n**Usage**: .prune 5") + .Alias(".clr") + .Description( + "`.prune` removes all WizBot'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.\n**Usage**: `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`") + .Parameter("user_or_num", ParameterType.Optional) + .Parameter("num", ParameterType.Optional) .Do(async e => { - if (!e.User.ServerPermissions.ManageMessages) return; - int val; - if (string.IsNullOrWhiteSpace(e.GetArg("num")) || !int.TryParse(e.GetArg("num"), out val) || val < 0) - return; - - foreach (var msg in await e.Channel.DownloadMessages(val).ConfigureAwait(false)) + if (string.IsNullOrWhiteSpace(e.GetArg("user_or_num"))) // if nothing is set, clear WizBot's messages, no permissions required { - await msg.Delete().ConfigureAwait(false); - await Task.Delay(100).ConfigureAwait(false); - } - }); + await Task.Run(async () => + { + var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == e.Server.CurrentUser.Id); + foreach (var m in msgs) + { + try + { + await m.Delete().ConfigureAwait(false); + } + catch { } + await Task.Delay(100).ConfigureAwait(false); + } - cgb.CreateCommand(Prefix + "die") - .Alias(Prefix + "graceful") - .Description("Shuts the bot down and notifies users about the restart. **Owner Only!**") - .Do(async e => - { - if (NadekoBot.IsOwner(e.User.Id)) + }).ConfigureAwait(false); + return; + } + if (!e.User.GetPermissions(e.Channel).ManageMessages) + return; + else if (!e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages) { - await e.Channel.SendMessage("`Shutting down.`").ConfigureAwait(false); - await Task.Delay(2000).ConfigureAwait(false); - Environment.Exit(0); + await e.Channel.SendMessage("💢I don't have the permission to manage messages."); + return; } - }); - - cgb.CreateCommand(Prefix + "clr") - .Description("Clears some of Nadeko's messages from the current channel. If given a user, will clear the user's messages from the current channel (**Owner Only!**) \n**Usage**: .clr @X") - .Parameter("user", ParameterType.Unparsed) - .Do(async e => - { - var usrId = NadekoBot.Client.CurrentUser.Id; - if (!string.IsNullOrWhiteSpace(e.GetArg("user")) && e.User.ServerPermissions.ManageMessages) + 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. { - var usr = e.Server.FindUsers(e.GetArg("user")).FirstOrDefault(); - if (usr != null) - usrId = usr.Id; + if (val <= 0) + return; + val++; + foreach (var msg in await e.Channel.DownloadMessages(val).ConfigureAwait(false)) + { + await msg.Delete().ConfigureAwait(false); + await Task.Delay(100).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; await Task.Run(async () => { - var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == usrId); + var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)).Where(m => m.User.Id == usr.Id).Take(val); foreach (var m in msgs) { try @@ -567,30 +666,55 @@ await Task.Run(async () => await m.Delete().ConfigureAwait(false); } catch { } - await Task.Delay(200).ConfigureAwait(false); + await Task.Delay(100).ConfigureAwait(false); } }).ConfigureAwait(false); }); + cgb.CreateCommand(Prefix + "die") + .Alias(Prefix + "graceful") + .Description("Shuts the bot down and notifies users about the restart. **Owner Only!**") + .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 + "newnick") + // .Alias(Prefix + "setnick") + // .Description("Give the bot a new nickname. You need manage server permissions.") + // .Parameter("new_nick", ParameterType.Unparsed) + // .AddCheck(SimpleCheckers.ManageServer()) + // .Do(async e => + // { + // if (e.GetArg("new_nick") == null) return; + + // await client.CurrentUser.Edit(WizBot.Creds.Password, e.GetArg("new_nick")).ConfigureAwait(false); + // }); + cgb.CreateCommand(Prefix + "newname") .Alias(Prefix + "setname") .Description("Give the bot a new name. **Owner Only!**") .Parameter("new_name", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id) || e.GetArg("new_name") == null) return; + if (e.GetArg("new_name") == null) return; - await client.CurrentUser.Edit(NadekoBot.Creds.Password, e.GetArg("new_name")).ConfigureAwait(false); + await client.CurrentUser.Edit(WizBot.Creds.Password, e.GetArg("new_name")).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "newavatar") .Alias(Prefix + "setavatar") - .Description("Sets a new avatar image for the NadekoBot. **Owner Only!**") + .Description("Sets a new avatar image for the WizBot. **Owner Only!**") .Parameter("img", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("img"))) + if (string.IsNullOrWhiteSpace(e.GetArg("img"))) return; // Gather user provided URL. var avatarAddress = e.GetArg("img"); @@ -598,7 +722,7 @@ await Task.Run(async () => var image = System.Drawing.Image.FromStream(imageStream); // Save the image to disk. image.Save("data/avatar.png", System.Drawing.Imaging.ImageFormat.Png); - await client.CurrentUser.Edit(NadekoBot.Creds.Password, avatar: image.ToStream()).ConfigureAwait(false); + await client.CurrentUser.Edit(WizBot.Creds.Password, avatar: image.ToStream()).ConfigureAwait(false); // Send confirm. await e.Channel.SendMessage("New avatar set.").ConfigureAwait(false); }); @@ -608,7 +732,7 @@ await Task.Run(async () => .Parameter("set_game", ParameterType.Unparsed) .Do(e => { - if (!NadekoBot.IsOwner(e.User.Id) || e.GetArg("set_game") == null) return; + if (!WizBot.IsOwner(e.User.Id) || e.GetArg("set_game") == null) return; client.SetGame(e.GetArg("set_game")); }); @@ -633,9 +757,9 @@ await Task.Run(async () => cgb.CreateCommand(Prefix + "commsuser") .Description("Sets a user for through-bot communication. Only works if server is set. Resets commschannel. **Owner Only!**") .Parameter("name", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; commsUser = commsServer?.FindUsers(e.GetArg("name")).FirstOrDefault(); if (commsUser != null) { @@ -649,9 +773,9 @@ await Task.Run(async () => cgb.CreateCommand(Prefix + "commsserver") .Description("Sets a server for through-bot communication. **Owner Only!**") .Parameter("server", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; commsServer = client.FindServers(e.GetArg("server")).FirstOrDefault(); if (commsServer != null) await e.Channel.SendMessage("Server for comms set.").ConfigureAwait(false); @@ -662,9 +786,9 @@ await Task.Run(async () => cgb.CreateCommand(Prefix + "commschannel") .Description("Sets a channel for through-bot communication. Only works if server is set. Resets commsuser. **Owner Only!**") .Parameter("ch", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; commsChannel = commsServer?.FindChannels(e.GetArg("ch"), ChannelType.Text).FirstOrDefault(); if (commsChannel != null) { @@ -676,11 +800,11 @@ await Task.Run(async () => }); cgb.CreateCommand(Prefix + "send") - .Description("Send a message to someone on a different server through the bot. **Owner Only!**\n **Usage**: .send Message text multi word!") + .Description("Send a message to someone on a different server through the bot. **Owner Only!**\n**Usage**: .send Message text multi word!") .Parameter("msg", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; if (commsUser != null) await commsUser.SendMessage(e.GetArg("msg")).ConfigureAwait(false); else if (commsChannel != null) @@ -721,12 +845,42 @@ await Task.Run(async () => }).ConfigureAwait(false); }); + cgb.CreateCommand(Prefix + "inrole") + .Description("Lists every person from the provided role or roles (separated by a ',') on this server.") + .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 = $"`Here is a list of users in a specfic role:`"; + 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.Name + "**#" + r.Discriminator)); + } + + 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 + "parsetosql") .Description("Loads exported parsedata from /data/parsedata/ into sqlite database.") + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) - return; await Task.Run(() => { SaveParseToDb("data/parsedata/Announcements.json"); @@ -742,7 +896,7 @@ await Task.Run(() => .AddCheck(SimpleCheckers.OwnerOnly()) .Do(e => { - NadekoBot.Client.MessageQueue.Clear(); + WizBot.Client.MessageQueue.Clear(); }); cgb.CreateCommand(Prefix + "donators") @@ -770,8 +924,6 @@ await Task.Run(async () => { await Task.Run(() => { - if (!NadekoBot.IsOwner(e.User.Id)) - return; var donator = e.Server.FindUsers(e.GetArg("donator")).FirstOrDefault(); var amount = int.Parse(e.GetArg("amount")); if (donator == null) return; @@ -781,7 +933,7 @@ await Task.Run(() => { Amount = amount, UserName = donator.Name, - UserId = (long)e.User.Id + UserId = (long)donator.Id }); e.Channel.SendMessage("Successfuly added a new donator. 👑"); } @@ -817,13 +969,42 @@ await Task.Run(() => .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - foreach (var ch in NadekoBot.Client.Servers.Select(s => s.DefaultChannel)) + foreach (var ch in WizBot.Client.Servers.Select(s => s.DefaultChannel)) { await ch.SendMessage(e.GetArg("msg")); } await e.Channel.SendMessage(":ok:"); }); + + cgb.CreateCommand(Prefix + "whoplays") + .Description("Shows a list of users who are playing the specified game.") + .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(); + + if (arr.Length == 0) + await e.Channel.SendMessage("Nobody. (not 100% sure)"); + else + await e.Channel.SendMessage("• " + string.Join("\n• ", arr)); + }); + + cgb.CreateCommand(Prefix + "updates") + .Alias(Prefix + "unotes") + .Description("Shows updates that have been done to WizBot.") + .Do(async e => + { + await e.Channel.SendMessage(" **WizBot Update Notes**\n\n").ConfigureAwait(false); + }); + }); } @@ -841,4 +1022,4 @@ public void SaveParseToDb(string where) where T : IDataModel catch { } } } -} +} \ No newline at end of file diff --git a/WizBot/Modules/Administration/Commands/AutoAssignRole.cs b/WizBot/Modules/Administration/Commands/AutoAssignRole.cs new file mode 100644 index 000000000..d3c2ae8ee --- /dev/null +++ b/WizBot/Modules/Administration/Commands/AutoAssignRole.cs @@ -0,0 +1,71 @@ +using Discord.Commands; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; +using System; +using System.Linq; + +namespace WizBot.Modules.Administration.Commands +{ + class AutoAssignRole : DiscordCommand + { + public AutoAssignRole(DiscordModule module) : base(module) + { + WizBot.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 + "aar") + .Alias(Module.Prefix + "autoassignrole") + .Description($"Automaticaly assigns a specified role to every user who joins the server. Type `.aar` to disable, `.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."); + } + 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.`"); + return; + } + var role = e.Server.FindRoles(r).FirstOrDefault(); + + if (role == null) + { + await e.Channel.SendMessage("💢 `Role not found.`"); + return; + } + + config.AutoAssignedRole = role.Id; + await e.Channel.SendMessage("`Auto assigned role is set.`"); + + }); + } + } +} \ No newline at end of file diff --git a/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs b/WizBot/Modules/Administration/Commands/CrossServerTextChannel.cs similarity index 91% rename from NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs rename to WizBot/Modules/Administration/Commands/CrossServerTextChannel.cs index 0d2a3fe74..3dece8a17 100644 --- a/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs +++ b/WizBot/Modules/Administration/Commands/CrossServerTextChannel.cs @@ -1,23 +1,23 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -namespace NadekoBot.Modules.Administration.Commands +namespace WizBot.Modules.Administration.Commands { class CrossServerTextChannel : DiscordCommand { public CrossServerTextChannel(DiscordModule module) : base(module) { - NadekoBot.Client.MessageReceived += async (s, e) => + WizBot.Client.MessageReceived += async (s, e) => { try { - if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; + if (e.User.Id == WizBot.Client.CurrentUser.Id) return; foreach (var subscriber in Subscribers) { var set = subscriber.Value; @@ -31,11 +31,11 @@ public CrossServerTextChannel(DiscordModule module) : base(module) } catch { } }; - NadekoBot.Client.MessageUpdated += async (s, e) => + WizBot.Client.MessageUpdated += async (s, e) => { try { - if (e.After?.User?.Id == null || e.After.User.Id == NadekoBot.Client.CurrentUser.Id) return; + if (e.After?.User?.Id == null || e.After.User.Id == WizBot.Client.CurrentUser.Id) return; foreach (var subscriber in Subscribers) { var set = subscriber.Value; diff --git a/WizBot/Modules/Administration/Commands/CustomReactionsCommands.cs b/WizBot/Modules/Administration/Commands/CustomReactionsCommands.cs new file mode 100644 index 000000000..8739d8206 --- /dev/null +++ b/WizBot/Modules/Administration/Commands/CustomReactionsCommands.cs @@ -0,0 +1,133 @@ +using Discord; +using Discord.Commands; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WizBot.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 + "addcustomreaction") + .Alias(Prefix + "acr") + .Description($"Add a custom reaction. Guide here: **Owner Only!** \n**Usage**: {Prefix}acr \"hello\" I love saying hello to %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 (WizBot.Config.CustomReactions.ContainsKey(name)) + WizBot.Config.CustomReactions[name].Add(message); + else + WizBot.Config.CustomReactions.Add(name, new System.Collections.Generic.List() { message }); + await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()); + await e.Channel.SendMessage($"Added {name} : {message}").ConfigureAwait(false); + + }); + + cgb.CreateCommand(Prefix + "listcustomreactions") + .Alias(Prefix + "lcr") + .Description($"Lists all current custom reactions (paginated with 5 commands per page).\n**Usage**:{Prefix}lcr 1") + .Parameter("num", ParameterType.Required) + .Do(async e => + { + int num; + if (!int.TryParse(e.GetArg("num"), out num) || num <= 0) return; + string result = GetCustomsOnPage(num - 1); //People prefer starting with 1 + await e.Channel.SendMessage(result); + }); + + cgb.CreateCommand(Prefix + "deletecustomreaction") + .Alias(Prefix + "dcr") + .Description("Deletes a custom reaction with given name (and index)") + .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 (!WizBot.Config.CustomReactions.ContainsKey(name)) + { + await e.Channel.SendMessage("Could not find given commandname"); + return; + } + string message = ""; + int index; + if (int.TryParse(e.GetArg("index")?.Trim() ?? "", out index)) + { + index = index - 1; + if (index < 0 || index > WizBot.Config.CustomReactions[name].Count) + { + await e.Channel.SendMessage("Given index was out of range").ConfigureAwait(false); + return; + + } + WizBot.Config.CustomReactions[name].RemoveAt(index); + if (!WizBot.Config.CustomReactions[name].Any()) + { + WizBot.Config.CustomReactions.Remove(name); + } + message = $"Deleted response #{index + 1} from `{name}`"; + } + else + { + WizBot.Config.CustomReactions.Remove(name); + message = $"Deleted custom reaction: `{name}`"; + } + await Task.Run(() => Classes.JSONModels.ConfigHandler.SaveConfig()); + await e.Channel.SendMessage(message); + }); + } + + private readonly int ItemsPerPage = 5; + + private string GetCustomsOnPage(int page) + { + var items = WizBot.Config.CustomReactions.Skip(page * ItemsPerPage).Take(ItemsPerPage); + if (!items.Any()) + { + return $"No items on page {page + 1}."; + } + 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 +//├ +//─ +//│ +//└ \ No newline at end of file diff --git a/NadekoBot/Modules/Administration/Commands/InfoCommands.cs b/WizBot/Modules/Administration/Commands/InfoCommands.cs similarity index 89% rename from NadekoBot/Modules/Administration/Commands/InfoCommands.cs rename to WizBot/Modules/Administration/Commands/InfoCommands.cs index be6e5ec1c..cd13c3f17 100644 --- a/NadekoBot/Modules/Administration/Commands/InfoCommands.cs +++ b/WizBot/Modules/Administration/Commands/InfoCommands.cs @@ -1,12 +1,12 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; +using WizBot.Classes; +using WizBot.Extensions; using System; using System.Linq; using System.Text; -namespace NadekoBot.Modules.Administration.Commands +namespace WizBot.Modules.Administration.Commands { class InfoCommands : DiscordCommand { @@ -25,7 +25,7 @@ internal override void Init(CommandGroupBuilder cgb) var servText = e.GetArg("server")?.Trim(); var server = string.IsNullOrWhiteSpace(servText) ? e.Server - : NadekoBot.Client.FindServers(servText).FirstOrDefault(); + : WizBot.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); @@ -83,8 +83,12 @@ internal override void Init(CommandGroupBuilder cgb) 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:` **{(string.IsNullOrWhiteSpace(user.CurrentGame) ? "-" : user.CurrentGame)}**"); + 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)}**"); @@ -92,4 +96,4 @@ internal override void Init(CommandGroupBuilder cgb) }); } } -} +} \ No newline at end of file diff --git a/WizBot/Modules/Administration/Commands/LogCommand.cs b/WizBot/Modules/Administration/Commands/LogCommand.cs new file mode 100644 index 000000000..68b7510ce --- /dev/null +++ b/WizBot/Modules/Administration/Commands/LogCommand.cs @@ -0,0 +1,368 @@ +using Discord; +using Discord.Commands; +using WizBot.Classes; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace WizBot.Modules.Administration.Commands +{ + internal class LogCommand : DiscordCommand + { + + private readonly ConcurrentDictionary logs = new ConcurrentDictionary(); + private readonly ConcurrentDictionary loggingPresences = new ConcurrentDictionary(); + private readonly ConcurrentDictionary voiceChannelLog = new ConcurrentDictionary(); + + private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】"; + + public LogCommand(DiscordModule module) : base(module) + { + WizBot.Client.MessageReceived += MsgRecivd; + WizBot.Client.MessageDeleted += MsgDltd; + WizBot.Client.MessageUpdated += MsgUpdtd; + WizBot.Client.UserUpdated += UsrUpdtd; + WizBot.Client.UserBanned += UsrBanned; + WizBot.Client.UserLeft += UsrLeft; + WizBot.Client.UserJoined += UsrJoined; + WizBot.Client.UserUnbanned += UsrUnbanned; + WizBot.Client.ChannelCreated += ChannelCreated; + WizBot.Client.ChannelDestroyed += ChannelDestroyed; + WizBot.Client.ChannelUpdated += ChannelUpdated; + + + WizBot.Client.MessageReceived += async (s, e) => + { + if (e.Channel.IsPrivate || e.User.Id == WizBot.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 { } + }; + } + + private async void ChannelUpdated(object sender, ChannelUpdatedEventArgs e) + { + try + { + Channel ch; + if (!logs.TryGetValue(e.Server, out ch)) + 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 + { + Channel ch; + if (!logs.TryGetValue(e.Server, out ch)) + 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 + { + Channel ch; + if (!logs.TryGetValue(e.Server, out ch)) + 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 + { + Channel ch; + if (!logs.TryGetValue(e.Server, out ch)) + 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 + { + Channel ch; + if (!logs.TryGetValue(e.Server, out ch)) + 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 + { + Channel ch; + if (!logs.TryGetValue(e.Server, out ch)) + 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 + { + Channel ch; + if (!logs.TryGetValue(e.Server, out ch)) + return; + await ch.SendMessage($"❗`{prettyCurrentTime}`❌`User banned:` **{e.User.Name}** ({e.User.Id})").ConfigureAwait(false); + } + catch { } + } + + public Func DoFunc() => async e => + { + Channel ch; + if (!logs.TryRemove(e.Server, out ch)) + { + logs.TryAdd(e.Server, e.Channel); + await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false); + return; + } + + await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false); + }; + + private async void MsgRecivd(object sender, MessageEventArgs e) + { + try + { + if (e.Server == null || e.Channel.IsPrivate || e.User.Id == WizBot.Client.CurrentUser.Id) + return; + Channel ch; + if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + return; + await ch.SendMessage( +$@"🕔`{prettyCurrentTime}` **New Message** `#{e.Channel.Name}` +👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Text.Unmention()}").ConfigureAwait(false); + } + catch { } + } + private async void MsgDltd(object sender, MessageEventArgs e) + { + try + { + if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == WizBot.Client.CurrentUser.Id) + return; + Channel ch; + if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + return; + await ch.SendMessage( +$@"🕔`{prettyCurrentTime}` **Message** 🚮 `#{e.Channel.Name}` +👤`{e.User?.ToString() ?? ("NULL")}` {e.Message.Text.Unmention()}").ConfigureAwait(false); + } + catch { } + } + private async void MsgUpdtd(object sender, MessageUpdatedEventArgs e) + { + try + { + if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == WizBot.Client.CurrentUser.Id) + return; + Channel ch; + if (!logs.TryGetValue(e.Server, out ch) || e.Channel == ch) + 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) + { + try + { + Channel ch; + if (loggingPresences.TryGetValue(e.Server, out ch)) + if (e.Before.Status != e.After.Status) + { + await ch.SendMessage($"`{prettyCurrentTime}`**{e.Before.Name}** is now **{e.After.Status}**.").ConfigureAwait(false); + } + } + catch { } + + try + { + 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 && voiceChannelLog.TryGetValue(beforeVch, out notifyChBefore)) + { + notifyLeave = true; + } + if (afterVch != null && voiceChannelLog.TryGetValue(afterVch, out notifyChAfter)) + { + 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 + { + Channel ch; + if (!logs.TryGetValue(e.Server, out ch)) + 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 #WizLOG 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.") + .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. **Owner Only!**") + .AddCheck(SimpleCheckers.OwnerOnly()) + .AddCheck(SimpleCheckers.ManageServer()) + .Do(DoFunc()); + + cgb.CreateCommand(Module.Prefix + "userpresence") + .Description("Starts logging to this channel when someone from the server goes online/offline/idle. **Owner Only!**") + .AddCheck(SimpleCheckers.OwnerOnly()) + .AddCheck(SimpleCheckers.ManageServer()) + .Do(async e => + { + Channel ch; + if (!loggingPresences.TryRemove(e.Server, out ch)) + { + loggingPresences.TryAdd(e.Server, e.Channel); + await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false); + return; + } + + 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. **Owner Only!**") + .Parameter("all", ParameterType.Optional) + .AddCheck(SimpleCheckers.OwnerOnly()) + .AddCheck(SimpleCheckers.ManageServer()) + .Do(async e => + { + + if (e.GetArg("all")?.ToLower() == "all") + { + foreach (var voiceChannel in e.Server.VoiceChannels) + { + voiceChannelLog.TryAdd(voiceChannel, e.Channel); + } + 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; + } + Channel throwaway; + if (!voiceChannelLog.TryRemove(e.User.VoiceChannel, out throwaway)) + { + voiceChannelLog.TryAdd(e.User.VoiceChannel, e.Channel); + 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); + }); + } + } +} \ No newline at end of file diff --git a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs b/WizBot/Modules/Administration/Commands/MessageRepeater.cs similarity index 69% rename from NadekoBot/Modules/Administration/Commands/MessageRepeater.cs rename to WizBot/Modules/Administration/Commands/MessageRepeater.cs index ca41ad412..681c1f268 100644 --- a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs +++ b/WizBot/Modules/Administration/Commands/MessageRepeater.cs @@ -1,12 +1,13 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; using System; using System.Collections.Concurrent; +using System.Threading.Tasks; using System.Timers; -namespace NadekoBot.Modules.Administration.Commands +namespace WizBot.Modules.Administration.Commands { class MessageRepeater : DiscordCommand { @@ -27,34 +28,52 @@ private class Repeater public Repeater Start() { MessageTimer = new Timer { Interval = Interval }; - MessageTimer.Elapsed += async (s, e) => + 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)) { - var ch = RepeatingChannel; - var msg = RepeatingMessage; - if (ch != null && !string.IsNullOrWhiteSpace(msg)) + try { - try - { - if (lastMessage != null) - await lastMessage.Delete().ConfigureAwait(false); - } - catch { } - try - { - lastMessage = await ch.SendMessage(msg).ConfigureAwait(false); - } - catch { } + if (lastMessage != null) + await lastMessage.Delete().ConfigureAwait(false); } - }; - return this; + 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.") + .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. Requires manage messages.") + "repeat is disabled. Requires manage messages.\n**Usage**:`.repeat 5 Hello there`") .Parameter("minutes", ParameterType.Optional) .Parameter("msg", ParameterType.Unparsed) .AddCheck(SimpleCheckers.ManageMessages()) @@ -73,7 +92,7 @@ internal override void Init(CommandGroupBuilder cgb) return; } int minutes; - if (!int.TryParse(minutesStr, out minutes) || minutes < 1 || minutes > 720) + if (!int.TryParse(minutesStr, out minutes) || minutes < 1 || minutes > 1440) { await e.Channel.SendMessage("Invalid value").ConfigureAwait(false); return; @@ -105,4 +124,4 @@ await e.Channel.SendMessage(String.Format("👌 Repeating `{0}` every " + public MessageRepeater(DiscordModule module) : base(module) { } } -} +} \ No newline at end of file diff --git a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs b/WizBot/Modules/Administration/Commands/PlayingRotate.cs similarity index 79% rename from NadekoBot/Modules/Administration/Commands/PlayingRotate.cs rename to WizBot/Modules/Administration/Commands/PlayingRotate.cs index 90ca7c209..01788a7ba 100644 --- a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs +++ b/WizBot/Modules/Administration/Commands/PlayingRotate.cs @@ -1,8 +1,8 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Modules.Music; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Classes.JSONModels; +using WizBot.Modules.Music; +using WizBot.Modules.Permissions.Classes; using System; using System.Collections.Generic; using System.Linq; @@ -10,7 +10,7 @@ using System.Threading.Tasks; using System.Timers; -namespace NadekoBot.Modules.Administration.Commands +namespace WizBot.Modules.Administration.Commands { internal class PlayingRotate : DiscordCommand { @@ -18,8 +18,8 @@ internal class PlayingRotate : DiscordCommand public static Dictionary> PlayingPlaceholders { get; } = new Dictionary> { - {"%servers%", () => NadekoBot.Client.Servers.Count().ToString()}, - {"%users%", () => NadekoBot.Client.Servers.SelectMany(s => s.Users).Count().ToString()}, + {"%servers%", () => WizBot.Client.Servers.Count().ToString()}, + {"%users%", () => WizBot.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(); @@ -49,25 +49,26 @@ public PlayingRotate(DiscordModule module) : base(module) var status = ""; lock (playingPlaceholderLock) { - if (PlayingPlaceholders.Count == 0) - return; - if (i >= PlayingPlaceholders.Count) + if (PlayingPlaceholders.Count == 0 + || WizBot.Config.RotatingStatuses.Count == 0 + || i >= PlayingPlaceholders.Count + || i >= WizBot.Config.RotatingStatuses.Count) { i = -1; return; } - status = NadekoBot.Config.RotatingStatuses[i]; + status = WizBot.Config.RotatingStatuses[i]; status = PlayingPlaceholders.Aggregate(status, (current, kvp) => current.Replace(kvp.Key, kvp.Value())); } if (string.IsNullOrWhiteSpace(status)) return; - Task.Run(() => { NadekoBot.Client.SetGame(status); }); + Task.Run(() => { WizBot.Client.SetGame(status); }); } catch { } }; - timer.Enabled = NadekoBot.Config.IsRotatingStatus; + timer.Enabled = WizBot.Config.IsRotatingStatus; } public Func DoFunc() => async e => @@ -78,7 +79,7 @@ public Func DoFunc() => async e => timer.Stop(); else timer.Start(); - NadekoBot.Config.IsRotatingStatus = timer.Enabled; + WizBot.Config.IsRotatingStatus = timer.Enabled; ConfigHandler.SaveConfig(); } await e.Channel.SendMessage($"❗`Rotating playing status has been {(timer.Enabled ? "enabled" : "disabled")}.`").ConfigureAwait(false); @@ -105,7 +106,7 @@ internal override void Init(CommandGroupBuilder cgb) return; lock (playingPlaceholderLock) { - NadekoBot.Config.RotatingStatuses.Add(arg); + WizBot.Config.RotatingStatuses.Add(arg); ConfigHandler.SaveConfig(); } await e.Channel.SendMessage("🆗 `Added a new playing string.`").ConfigureAwait(false); @@ -117,13 +118,13 @@ internal override void Init(CommandGroupBuilder cgb) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (NadekoBot.Config.RotatingStatuses.Count == 0) + if (WizBot.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++) + for (var i = 0; i < WizBot.Config.RotatingStatuses.Count; i++) { - sb.AppendLine($"`{i + 1}.` {NadekoBot.Config.RotatingStatuses[i]}"); + sb.AppendLine($"`{i + 1}.` {WizBot.Config.RotatingStatuses[i]}"); } await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); }); @@ -140,10 +141,10 @@ await e.Channel.SendMessage("`There are no playing strings. " + string str; lock (playingPlaceholderLock) { - if (!int.TryParse(arg.Trim(), out num) || num <= 0 || num > NadekoBot.Config.RotatingStatuses.Count) + if (!int.TryParse(arg.Trim(), out num) || num <= 0 || num > WizBot.Config.RotatingStatuses.Count) return; - str = NadekoBot.Config.RotatingStatuses[num - 1]; - NadekoBot.Config.RotatingStatuses.RemoveAt(num - 1); + str = WizBot.Config.RotatingStatuses[num - 1]; + WizBot.Config.RotatingStatuses.RemoveAt(num - 1); ConfigHandler.SaveConfig(); } await e.Channel.SendMessage($"🆗 `Removed playing string #{num}`({str})").ConfigureAwait(false); diff --git a/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs b/WizBot/Modules/Administration/Commands/RatelimitCommand.cs similarity index 90% rename from NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs rename to WizBot/Modules/Administration/Commands/RatelimitCommand.cs index d300e29af..a4860c816 100644 --- a/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs +++ b/WizBot/Modules/Administration/Commands/RatelimitCommand.cs @@ -1,10 +1,10 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; using System; using System.Collections.Concurrent; -namespace NadekoBot.Modules.Administration.Commands +namespace WizBot.Modules.Administration.Commands { internal class RatelimitCommand : DiscordCommand { @@ -15,9 +15,9 @@ internal class RatelimitCommand : DiscordCommand public RatelimitCommand(DiscordModule module) : base(module) { - NadekoBot.Client.MessageReceived += async (s, e) => + WizBot.Client.MessageReceived += async (s, e) => { - if (e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id) + if (e.Channel.IsPrivate || e.User.Id == WizBot.Client.CurrentUser.Id) return; ConcurrentDictionary userTimePair; if (!RatelimitingChannels.TryGetValue(e.Channel.Id, out userTimePair)) return; diff --git a/NadekoBot/Modules/Administration/Commands/Remind.cs b/WizBot/Modules/Administration/Commands/Remind.cs similarity index 77% rename from NadekoBot/Modules/Administration/Commands/Remind.cs rename to WizBot/Modules/Administration/Commands/Remind.cs index 51741198b..fabbc6467 100644 --- a/NadekoBot/Modules/Administration/Commands/Remind.cs +++ b/WizBot/Modules/Administration/Commands/Remind.cs @@ -1,14 +1,15 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.DataModels; +using WizBot.Classes; +using WizBot.DataModels; +using WizBot.Modules.Permissions.Classes; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Timers; -namespace NadekoBot.Modules.Administration.Commands +namespace WizBot.Modules.Administration.Commands { class Remind : DiscordCommand { @@ -18,6 +19,13 @@ class Remind : DiscordCommand 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(); @@ -43,17 +51,21 @@ private Timer StartNewReminder(Reminder r) Channel ch; if (r.IsPrivate) { - ch = NadekoBot.Client.PrivateChannels.FirstOrDefault(c => (long)c.Id == r.ChannelId); + ch = WizBot.Client.PrivateChannels.FirstOrDefault(c => (long)c.Id == r.ChannelId); if (ch == null) - ch = await NadekoBot.Client.CreatePrivateChannel((ulong)r.ChannelId).ConfigureAwait(false); + ch = await WizBot.Client.CreatePrivateChannel((ulong)r.ChannelId).ConfigureAwait(false); } else - ch = NadekoBot.Client.GetServer((ulong)r.ServerId)?.GetChannel((ulong)r.ChannelId); + ch = WizBot.Client.GetServer((ulong)r.ServerId)?.GetChannel((ulong)r.ChannelId); if (ch == null) return; - await ch.SendMessage($"❗⏰**I've been told to remind you to '{r.Message}' now by <@{r.UserId}>.**⏰❗").ConfigureAwait(false); + await ch.SendMessage( + replacements.Aggregate(WizBot.Config.RemindMessageFormat, + (cur, replace) => cur.Replace(replace.Key, replace.Value(r))) + ).ConfigureAwait(false); //it works trust me + } catch (Exception ex) { @@ -163,8 +175,23 @@ internal override void Init(CommandGroupBuilder cgb) 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:m})").ConfigureAwait(false); + 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. **Owner only!**") + .Parameter("msg", ParameterType.Unparsed) + .AddCheck(SimpleCheckers.OwnerOnly()) + .Do(async e => + { + var arg = e.GetArg("msg")?.Trim(); + if (string.IsNullOrWhiteSpace(arg)) + return; + + WizBot.Config.RemindMessageFormat = arg; + await e.Channel.SendMessage("`New remind message set.`"); + }); } } } diff --git a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/WizBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs similarity index 92% rename from NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs rename to WizBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs index f61dc35d0..1affa6459 100644 --- a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs +++ b/WizBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs @@ -1,11 +1,12 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; -namespace NadekoBot.Modules.Administration.Commands +namespace WizBot.Modules.Administration.Commands { internal class SelfAssignedRolesCommand : DiscordCommand { @@ -120,8 +121,15 @@ internal override void Init(CommandGroupBuilder cgb) await e.Channel.SendMessage($":anger:You already have {role.Name} role.").ConfigureAwait(false); return; } - await e.User.AddRoles(role).ConfigureAwait(false); - await e.Channel.SendMessage($":ok:You now have {role.Name} role.").ConfigureAwait(false); + await e.User.AddRoles(role).ConfigureAwait(false); + var msg = await e.Channel.SendMessage($":ok:You now have {role.Name} role.").ConfigureAwait(false); + await Task.Delay(3000); + await msg.Delete(); + try + { + await e.Message.Delete(); + } + catch { } }); cgb.CreateCommand(".iamn") diff --git a/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs b/WizBot/Modules/Administration/Commands/ServerGreetCommand.cs similarity index 68% rename from NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs rename to WizBot/Modules/Administration/Commands/ServerGreetCommand.cs index de915b73f..fe7449dd6 100644 --- a/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs +++ b/WizBot/Modules/Administration/Commands/ServerGreetCommand.cs @@ -1,6 +1,6 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; @@ -10,15 +10,13 @@ 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 +namespace WizBot.Modules.Administration.Commands { internal class ServerGreetCommand : DiscordCommand { @@ -31,8 +29,8 @@ public ServerGreetCommand(DiscordModule module) : base(module) { AnnouncementsDictionary = new ConcurrentDictionary(); - NadekoBot.Client.UserJoined += UserJoined; - NadekoBot.Client.UserLeft += UserLeft; + WizBot.Client.UserJoined += UserJoined; + WizBot.Client.UserLeft += UserLeft; var data = Classes.DbHandler.Instance.GetAllRows(); @@ -49,7 +47,7 @@ private async void UserLeft(object sender, UserEventArgs e) !AnnouncementsDictionary[e.Server.Id].Bye) return; var controls = AnnouncementsDictionary[e.Server.Id]; - var channel = NadekoBot.Client.GetChannel(controls.ByeChannel); + var channel = WizBot.Client.GetChannel(controls.ByeChannel); var msg = controls.ByeText.Replace("%user%", "**" + e.User.Name + "**").Trim(); if (string.IsNullOrEmpty(msg)) return; @@ -69,9 +67,9 @@ private async void UserLeft(object sender, UserEventArgs e) if (channel == null) return; Greeted++; var toDelete = await channel.SendMessage(msg).ConfigureAwait(false); - if (e.Server.CurrentUser.GetPermissions(channel).ManageMessages) + if (e.Server.CurrentUser.GetPermissions(channel).ManageMessages && controls.DeleteGreetMessages) { - await Task.Delay(300000).ConfigureAwait(false); // 5 minutes + await Task.Delay(30000).ConfigureAwait(false); // 5 minutes await toDelete.Delete().ConfigureAwait(false); } } @@ -87,7 +85,7 @@ private async void UserJoined(object sender, Discord.UserEventArgs e) !AnnouncementsDictionary[e.Server.Id].Greet) return; var controls = AnnouncementsDictionary[e.Server.Id]; - var channel = NadekoBot.Client.GetChannel(controls.GreetChannel); + var channel = WizBot.Client.GetChannel(controls.GreetChannel); var msg = controls.GreetText.Replace("%user%", e.User.Mention).Trim(); if (string.IsNullOrEmpty(msg)) @@ -102,9 +100,9 @@ private async void UserJoined(object sender, Discord.UserEventArgs e) if (channel == null) return; Greeted++; var toDelete = await channel.SendMessage(msg).ConfigureAwait(false); - if (e.Server.CurrentUser.GetPermissions(channel).ManageMessages) + if (e.Server.CurrentUser.GetPermissions(channel).ManageMessages && controls.DeleteGreetMessages) { - await Task.Delay(300000).ConfigureAwait(false); // 5 minutes + await Task.Delay(30000).ConfigureAwait(false); // 5 minutes await toDelete.Delete().ConfigureAwait(false); } } @@ -116,50 +114,71 @@ public class AnnounceControls { private DataModels.Announcement _model { get; } - public bool Greet { + public bool Greet + { get { return _model.Greet; } set { _model.Greet = value; Save(); } } - public ulong GreetChannel { + public ulong GreetChannel + { get { return (ulong)_model.GreetChannelId; } set { _model.GreetChannelId = (long)value; Save(); } } - public bool GreetPM { + public bool GreetPM + { get { return _model.GreetPM; } set { _model.GreetPM = value; Save(); } } - public bool ByePM { + public bool ByePM + { get { return _model.ByePM; } set { _model.ByePM = value; Save(); } } - public string GreetText { + public string GreetText + { get { return _model.GreetText; } set { _model.GreetText = value; Save(); } } - public bool Bye { + public bool Bye + { get { return _model.Bye; } set { _model.Bye = value; Save(); } } - public ulong ByeChannel { + public ulong ByeChannel + { get { return (ulong)_model.ByeChannelId; } set { _model.ByeChannelId = (long)value; Save(); } } - public string ByeText { + public string ByeText + { get { return _model.ByeText; } set { _model.ByeText = value; Save(); } } - public ulong ServerId { + 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; @@ -196,6 +215,8 @@ internal bool ToggleGreet(ulong id) return Greet = true; } } + + internal bool ToggleDelete() => DeleteGreetMessages = !DeleteGreetMessages; internal bool ToggleGreetPM() => GreetPM = !GreetPM; internal bool ToggleByePM() => ByePM = !ByePM; @@ -207,68 +228,80 @@ private void Save() internal override void Init(CommandGroupBuilder cgb) { + cgb.CreateCommand(Module.Prefix + "grdel") + .Description("Toggles automatic deletion of greet and bye messages.") + .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("Enables or Disables anouncements on the current channel when someone joins the server.") + .Description("Toggles announcements on the current channel when someone joins the server.") .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; - if (!AnnouncementsDictionary.ContainsKey(e.Server.Id)) - AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - var controls = AnnouncementsDictionary[e.Server.Id]; + var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - if (controls.ToggleGreet(e.Channel.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 announce message. Type %user% if you want to mention the new member.\n**Usage**: .greetmsg Welcome to the server, %user%.") + .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.\n**Usage**: .greetmsg Welcome to the server, %user%.") .Parameter("msg", ParameterType.Unparsed) .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; - if (e.GetArg("msg") == null) return; - if (!AnnouncementsDictionary.ContainsKey(e.Server.Id)) - AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - AnnouncementsDictionary[e.Server.Id].GreetText = e.GetArg("msg"); + 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 (!AnnouncementsDictionary[e.Server.Id].Greet) + if (!ann.Greet) await e.Channel.SendMessage("Enable greet messsages by typing `.greet`").ConfigureAwait(false); }); cgb.CreateCommand(Module.Prefix + "bye") - .Description("Enables or Disables anouncements on the current channel when someone leaves the server.") + .Description("Toggles anouncements on the current channel when someone leaves the server.") .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; - if (!AnnouncementsDictionary.ContainsKey(e.Server.Id)) - AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - var controls = AnnouncementsDictionary[e.Server.Id]; + var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - if (controls.ToggleBye(e.Channel.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 announce leave message. Type %user% if you want to mention the new member.\n**Usage**: .byemsg %user% has left the server.") + .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.\n**Usage**: .byemsg %user% has left the server.") .Parameter("msg", ParameterType.Unparsed) .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; - if (e.GetArg("msg") == null) return; - if (!AnnouncementsDictionary.ContainsKey(e.Server.Id)) - AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - - AnnouncementsDictionary[e.Server.Id].ByeText = e.GetArg("msg"); + 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 (!AnnouncementsDictionary[e.Server.Id].Bye) + if (!ann.Bye) await e.Channel.SendMessage("Enable bye messsages by typing `.bye`.").ConfigureAwait(false); }); @@ -277,15 +310,14 @@ internal override void Init(CommandGroupBuilder cgb) .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; - if (!AnnouncementsDictionary.ContainsKey(e.Server.Id)) - AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); + var ann = AnnouncementsDictionary.GetOrAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); + - AnnouncementsDictionary[e.Server.Id].ToggleByePM(); - if (AnnouncementsDictionary[e.Server.Id].ByePM) + 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 (!AnnouncementsDictionary[e.Server.Id].Bye) + if (!ann.Bye) await e.Channel.SendMessage("Enable bye messsages by typing `.bye`, and set the bye message using `.byemsg`").ConfigureAwait(false); }); @@ -294,17 +326,16 @@ internal override void Init(CommandGroupBuilder cgb) .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; - if (!AnnouncementsDictionary.ContainsKey(e.Server.Id)) - AnnouncementsDictionary.TryAdd(e.Server.Id, new AnnounceControls(e.Server.Id)); - AnnouncementsDictionary[e.Server.Id].ToggleGreetPM(); - if (AnnouncementsDictionary[e.Server.Id].GreetPM) + 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 (!AnnouncementsDictionary[e.Server.Id].Greet) + if (!ann.Greet) await e.Channel.SendMessage("Enable greet messsages by typing `.greet`, and set the greet message using `.greetmsg`").ConfigureAwait(false); }); } } -} +} \ No newline at end of file diff --git a/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs b/WizBot/Modules/Administration/Commands/VoiceNotificationCommand.cs similarity index 95% rename from NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs rename to WizBot/Modules/Administration/Commands/VoiceNotificationCommand.cs index b17fd1a98..421bc4ae5 100644 --- a/NadekoBot/Modules/Administration/Commands/VoiceNotificationCommand.cs +++ b/WizBot/Modules/Administration/Commands/VoiceNotificationCommand.cs @@ -1,12 +1,12 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; using System; using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; -namespace NadekoBot.Modules.Administration.Commands +namespace WizBot.Modules.Administration.Commands { internal class VoiceNotificationCommand : DiscordCommand { diff --git a/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs b/WizBot/Modules/Administration/Commands/VoicePlusTextCommand.cs similarity index 75% rename from NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs rename to WizBot/Modules/Administration/Commands/VoicePlusTextCommand.cs index a1e1df949..be245aa88 100644 --- a/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs +++ b/WizBot/Modules/Administration/Commands/VoicePlusTextCommand.cs @@ -1,20 +1,23 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Extensions; +using WizBot.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 +namespace WizBot.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.Client.UserUpdated += async (sender, e) => + WizBot.Client.UserUpdated += async (sender, e) => { try { @@ -24,7 +27,7 @@ public VoicePlusTextCommand(DiscordModule module) : base(module) if (e.Before.VoiceChannel == e.After.VoiceChannel) return; if (!config.VoicePlusTextEnabled) return; - var serverPerms = e.Server.GetUser(NadekoBot.Client.CurrentUser.Id)?.ServerPermissions; + var serverPerms = e.Server.GetUser(WizBot.Client.CurrentUser.Id)?.ServerPermissions; if (serverPerms == null) return; if (!serverPerms.Value.ManageChannels || !serverPerms.Value.ManageRoles) @@ -79,10 +82,41 @@ await textChannel.AddPermissionsRule(e.After, } private string GetChannelName(string voiceName) => - voiceName.Replace(" ", "-").Trim() + "-voice"; + channelNameRegex.Replace(voiceName, "").Trim().Replace(" ", "-").TrimTo(90, true) + "-voice"; internal override void Init(CommandGroupBuilder cgb) { + cgb.CreateCommand(Module.Prefix + "cleanv+t") + .Description("Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk.**") + .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 + "v+t") .Alias(Module.Prefix + "voice+text") .Description("Creates a text channel for each voice channel only users in that voice channel can see." + diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs b/WizBot/Modules/ClashOfClans/ClashOfClans.cs similarity index 99% rename from NadekoBot/Modules/ClashOfClans/ClashOfClans.cs rename to WizBot/Modules/ClashOfClans/ClashOfClans.cs index 851689715..74ea9089b 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClans.cs +++ b/WizBot/Modules/ClashOfClans/ClashOfClans.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; -namespace NadekoBot.Classes.ClashOfClans +namespace WizBot.Classes.ClashOfClans { internal class Caller { diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs b/WizBot/Modules/ClashOfClans/ClashOfClansModule.cs similarity index 98% rename from NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs rename to WizBot/Modules/ClashOfClans/ClashOfClansModule.cs index 026d2eca5..1731a7299 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs +++ b/WizBot/Modules/ClashOfClans/ClashOfClansModule.cs @@ -1,16 +1,16 @@ using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes.ClashOfClans; +using WizBot.Classes.ClashOfClans; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Text; -namespace NadekoBot.Modules.ClashOfClans +namespace WizBot.Modules.ClashOfClans { internal class ClashOfClansModule : DiscordModule { - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.ClashOfClans; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.ClashOfClans; public static ConcurrentDictionary> ClashWars { get; } = new ConcurrentDictionary>(); diff --git a/NadekoBot/Modules/Conversations/Commands/CopyCommand.cs b/WizBot/Modules/Conversations/Commands/CopyCommand.cs similarity index 81% rename from NadekoBot/Modules/Conversations/Commands/CopyCommand.cs rename to WizBot/Modules/Conversations/Commands/CopyCommand.cs index 48ef01f4d..0a1efda9f 100644 --- a/NadekoBot/Modules/Conversations/Commands/CopyCommand.cs +++ b/WizBot/Modules/Conversations/Commands/CopyCommand.cs @@ -1,10 +1,10 @@ using Discord.Commands; -using NadekoBot.Modules; +using WizBot.Modules; using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace NadekoBot.Classes.Conversations.Commands +namespace WizBot.Classes.Conversations.Commands { internal class CopyCommand : DiscordCommand { @@ -12,12 +12,12 @@ internal class CopyCommand : DiscordCommand public CopyCommand(DiscordModule module) : base(module) { - NadekoBot.Client.MessageReceived += Client_MessageReceived; + WizBot.Client.MessageReceived += Client_MessageReceived; } private async void Client_MessageReceived(object sender, Discord.MessageEventArgs e) { - if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; + if (e.User.Id == WizBot.Client.CurrentUser.Id) return; try { if (string.IsNullOrWhiteSpace(e.Message.Text)) @@ -42,12 +42,12 @@ internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand("copyme") .Alias("cm") - .Description("Nadeko starts copying everything you say. Disable with cs") + .Description("Wiz Bot starts copying everything you say. Disable with cs") .Do(DoFunc()); cgb.CreateCommand("cs") .Alias("copystop") - .Description("Nadeko stops copying you") + .Description("Wiz Bot stops copying you") .Do(StopCopy()); } diff --git a/NadekoBot/Modules/Conversations/Commands/RequestsCommand.cs b/WizBot/Modules/Conversations/Commands/RequestsCommand.cs similarity index 57% rename from NadekoBot/Modules/Conversations/Commands/RequestsCommand.cs rename to WizBot/Modules/Conversations/Commands/RequestsCommand.cs index dfaa49193..a92bff20a 100644 --- a/NadekoBot/Modules/Conversations/Commands/RequestsCommand.cs +++ b/WizBot/Modules/Conversations/Commands/RequestsCommand.cs @@ -1,10 +1,11 @@ using Discord.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules; +using WizBot.Extensions; +using WizBot.Modules; +using WizBot.Modules.Permissions.Classes; using System; using System.Threading.Tasks; -namespace NadekoBot.Classes.Conversations.Commands +namespace WizBot.Classes.Conversations.Commands { internal class RequestsCommand : DiscordCommand { @@ -25,13 +26,13 @@ public string GetRequests() { var task = DbHandler.Instance.GetAllRows(); - var str = "Here are all current requests for NadekoBot:\n\n"; + var str = "Here are all current requests for WizBot:\n\n"; foreach (var reqObj in task) { str += $"{reqObj.Id}. by **{reqObj.UserName}** from **{reqObj.ServerName}** at {reqObj.DateAdded.ToLocalTime()}\n" + $"**{reqObj.RequestText}**\n----------\n"; } - return str + "\n__Type [@NadekoBot clr] to clear all of my messages.__"; + return str + "\n__Type [@WizBot clr] to clear all of my messages.__"; } public bool DeleteRequest(int requestNumber) => @@ -49,7 +50,7 @@ internal override void Init(CommandGroupBuilder cgb) cgb.CreateCommand("req") .Alias("request") - .Description("Requests a feature for nadeko.\n**Usage**: @NadekoBot req new_feature") + .Description("Requests a feature for WizBot.\n**Usage**: @WizBot req new_feature") .Parameter("all", ParameterType.Unparsed) .Do(async e => { @@ -68,7 +69,7 @@ internal override void Init(CommandGroupBuilder cgb) }); cgb.CreateCommand("lr") - .Description("PMs the user all current nadeko requests.") + .Description("PMs the user all current WizBot requests.") .Do(async e => { var str = await Task.Run(() => GetRequests()).ConfigureAwait(false); @@ -79,60 +80,54 @@ internal override void Init(CommandGroupBuilder cgb) }); cgb.CreateCommand("dr") - .Description("Deletes a request. Only owner is able to do this.") + .Description("Deletes a request. **Owner Only!**") .Parameter("reqNumber", ParameterType.Required) + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (NadekoBot.IsOwner(e.User.Id)) + try { - try + if (DeleteRequest(int.Parse(e.Args[0]))) { - if (DeleteRequest(int.Parse(e.Args[0]))) - { - await e.Channel.SendMessage(e.User.Mention + " Request deleted.").ConfigureAwait(false); - } - else - { - await e.Channel.SendMessage("No request on that number.").ConfigureAwait(false); - } + await e.Channel.SendMessage(e.User.Mention + " Request deleted.").ConfigureAwait(false); } - catch + else { - await e.Channel.SendMessage("Error deleting request, probably NaN error.").ConfigureAwait(false); + await e.Channel.SendMessage("No request on that number.").ConfigureAwait(false); } } - else await e.Channel.SendMessage("You don't have permission to do that.").ConfigureAwait(false); + catch + { + await e.Channel.SendMessage("Error deleting request, probably NaN error.").ConfigureAwait(false); + } }); cgb.CreateCommand("rr") - .Description("Resolves a request. Only owner is able to do this.") + .Description("Resolves a request. **Owner Only!**") .Parameter("reqNumber", ParameterType.Required) + .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { - if (NadekoBot.IsOwner(e.User.Id)) + try { - try + var sc = ResolveRequest(int.Parse(e.Args[0])); + if (sc != null) { - var sc = ResolveRequest(int.Parse(e.Args[0])); - if (sc != null) - { - await e.Channel.SendMessage(e.User.Mention + " Request resolved, notice sent.").ConfigureAwait(false); - await NadekoBot.Client.GetServer((ulong)sc.ServerId).GetUser((ulong)sc.UserId).Send("**This request of yours has been resolved:**\n" + sc.RequestText).ConfigureAwait(false); - } - else - { - await e.Channel.SendMessage("No request on that number.").ConfigureAwait(false); - } + await e.Channel.SendMessage(e.User.Mention + " Request resolved, notice sent.").ConfigureAwait(false); + await WizBot.Client.GetServer((ulong)sc.ServerId).GetUser((ulong)sc.UserId).Send("**This request of yours has been resolved:**\n" + sc.RequestText).ConfigureAwait(false); } - catch + else { - await e.Channel.SendMessage("Error resolving request, probably NaN error.").ConfigureAwait(false); + await e.Channel.SendMessage("No request on that number.").ConfigureAwait(false); } } - else await e.Channel.SendMessage("You don't have permission to do that.").ConfigureAwait(false); + catch + { + await e.Channel.SendMessage("Error resolving request, probably NaN error.").ConfigureAwait(false); + } }); } public RequestsCommand(DiscordModule module) : base(module) { } } -} +} \ No newline at end of file diff --git a/NadekoBot/Modules/Conversations/Conversations.cs b/WizBot/Modules/Conversations/Conversations.cs similarity index 53% rename from NadekoBot/Modules/Conversations/Conversations.cs rename to WizBot/Modules/Conversations/Conversations.cs index f01b2ac65..6b2123b9e 100644 --- a/NadekoBot/Modules/Conversations/Conversations.cs +++ b/WizBot/Modules/Conversations/Conversations.cs @@ -1,10 +1,11 @@ using Discord; using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes.Conversations.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; -using NadekoBot.Properties; +using WizBot.Classes.Conversations.Commands; +using WizBot.DataModels; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; +using WizBot.Properties; using System; using System.Diagnostics; using System.Drawing; @@ -13,7 +14,7 @@ using System.Linq; using System.Threading.Tasks; -namespace NadekoBot.Modules.Conversations +namespace WizBot.Modules.Conversations { internal class Conversations : DiscordModule { @@ -24,7 +25,7 @@ public Conversations() commands.Add(new RequestsCommand(this)); } - public override string Prefix { get; } = String.Format(NadekoBot.Config.CommandPrefixes.Conversations, NadekoBot.Creds.BotId); + public override string Prefix { get; } = String.Format(WizBot.Config.CommandPrefixes.Conversations, WizBot.Creds.BotId); public override void Install(ModuleManager manager) { @@ -34,47 +35,6 @@ public override void Install(ModuleManager manager) { cgb.AddCheck(PermissionChecker.Instance); - cgb.CreateCommand("e") - .Description("You did it. Or someone else!") - .Parameter("other", ParameterType.Unparsed) - .Do(async e => - { - var other = e.GetArg("other"); - if (string.IsNullOrWhiteSpace(other)) - await e.Channel.SendMessage($"{e.User.Name} did it. 😒 🔫").ConfigureAwait(false); - else - await e.Channel.SendMessage($"{other} did it. 😒 🔫").ConfigureAwait(false); - }); - - cgb.CreateCommand("comeatmebro") - .Description("Come at me bro (ง’̀-‘́)ง \n**Usage**: comeatmebro {target}") - .Parameter("target", ParameterType.Optional) - .Do(async e => - { - var usr = e.Server.FindUsers(e.GetArg("target")).FirstOrDefault(); - if (usr == null) - { - await e.Channel.SendMessage("(ง’̀-‘́)ง").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage($"{usr.Mention} (ง’̀-‘́)ง").ConfigureAwait(false); - }); - - - cgb.CreateCommand("\\o\\") - .Description("Nadeko replies with /o/") - .Do(async e => await e.Channel.SendMessage(e.User.Mention + "/o/").ConfigureAwait(false)); - - cgb.CreateCommand("/o/") - .Description("Nadeko replies with \\o\\") - .Do(async e => await e.Channel.SendMessage(e.User.Mention + "\\o\\").ConfigureAwait(false)); - - cgb.CreateCommand("moveto") - .Description("Suggests moving the conversation.\n**Usage**: moveto #spam") - .Parameter("target", ParameterType.Unparsed) - .Do(async e => await e.Channel.SendMessage($"(👉 ͡° ͜ʖ ͡°)👉 {e.GetArg("target")}")); - - cgb.CreateCommand("..") .Description("Adds a new quote with the specified name (single word) and message (no limit).\n**Usage**: .. abc My message") .Parameter("keyword", ParameterType.Required) @@ -114,9 +74,29 @@ await Task.Run(() => 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.\n**Usage**: `..qdel abc`") + .Parameter("quote", ParameterType.Required) + .Do(async e => + { + var text = e.GetArg("quote")?.Trim(); + if (string.IsNullOrWhiteSpace(text)) + return; + await Task.Run(() => + { + if (WizBot.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 => + manager.CreateCommands(WizBot.BotMention, cgb => { var client = manager.Client; @@ -125,7 +105,7 @@ await Task.Run(() => commands.ForEach(cmd => cmd.Init(cgb)); cgb.CreateCommand("uptime") - .Description("Shows how long Nadeko has been running for.") + .Description("Shows how long WizBot has been running for.") .Do(async e => { var time = (DateTime.Now - Process.GetCurrentProcess().StartTime); @@ -137,7 +117,7 @@ await Task.Run(() => .Description("Works only for the owner. Shuts the bot down.") .Do(async e => { - if (NadekoBot.IsOwner(e.User.Id)) + if (WizBot.IsOwner(e.User.Id)) { await e.Channel.SendMessage(e.User.Mention + ", Yes, my love.").ConfigureAwait(false); await Task.Delay(5000).ConfigureAwait(false); @@ -154,7 +134,7 @@ await Task.Run(() => .Description("Replies with positive answer only to the bot owner.") .Do(async e => { - if (NadekoBot.IsOwner(e.User.Id)) + if (WizBot.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); @@ -165,12 +145,12 @@ await Task.Run(() => .Description("Replies positive only if bot owner is online.") .Do(async e => { - if (NadekoBot.IsOwner(e.User.Id)) + if (WizBot.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]); + var kw = e.Server.GetUser(WizBot.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); @@ -181,125 +161,8 @@ await Task.Run(() => } }); - cgb.CreateCommand("insult") - .Parameter("mention", ParameterType.Required) - .Description("Insults @X person.\n**Usage**: @NadekoBot insult @X.") - .Do(async e => - { - var u = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); - if (u == null) - { - await e.Channel.SendMessage("Invalid user specified.").ConfigureAwait(false); - return; - } - - if (NadekoBot.IsOwner(u.Id)) - { - await e.Channel.SendMessage("I would never insult my master <3").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage(u.Mention + NadekoBot.Locale.Insults[rng.Next(0, NadekoBot.Locale.Insults.Length)]).ConfigureAwait(false); - }); - - cgb.CreateCommand("praise") - .Description("Praises @X person.\n**Usage**: @NadekoBot praise @X.") - .Parameter("mention", ParameterType.Required) - .Do(async e => - { - var u = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); - - if (u == null) - { - await e.Channel.SendMessage("Invalid user specified.").ConfigureAwait(false); - return; - } - - if (NadekoBot.IsOwner(u.Id)) - { - await e.Channel.SendMessage(e.User.Mention + " I don't need your permission to praise my beloved Master <3").ConfigureAwait(false); - return; - } - await e.Channel.SendMessage(u.Mention + NadekoBot.Locale.Praises[rng.Next(0, NadekoBot.Locale.Praises.Length)]).ConfigureAwait(false); - }); - - cgb.CreateCommand("pat") - .Description("Pat someone ^_^") - .Parameter("user", ParameterType.Unparsed) - .Do(async e => - { - var userStr = e.GetArg("user"); - if (string.IsNullOrWhiteSpace(userStr) || !e.Message.MentionedUsers.Any()) return; - var user = e.Server.FindUsers(userStr).FirstOrDefault(); - if (user == null) - return; - try - { - await e.Channel.SendMessage( - $"{user.Mention} " + - $"{NadekoBot.Config.PatResponses[rng.Next(0, NadekoBot.Config.PatResponses.Length)]}") - .ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Error while handling PatResponses check your data/config.json").ConfigureAwait(false); - } - }); - - cgb.CreateCommand("cry") - .Description("Tell Nadeko to cry. You are a heartless monster if you use this command.") - .Do(async e => - { - try - { - await - e.Channel.SendMessage( - $"(•̥́ _•ૅ。)\n{NadekoBot.Config.CryResponses[rng.Next(0, NadekoBot.Config.CryResponses.Length)]}") - .ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Error while handling CryResponses check your data/config.json").ConfigureAwait(false); - } - }); - - cgb.CreateCommand("disguise") - .Description("Tell Nadeko to disguise herself.") - .Do(async e => - { - try - { - await - e.Channel.SendMessage( - $"{NadekoBot.Config.DisguiseResponses[rng.Next(0, NadekoBot.Config.DisguiseResponses.Length)]}") - .ConfigureAwait(false); - } - catch - { - await e.Channel.SendMessage("Error while handling DisguiseResponses check your data/config.json") - .ConfigureAwait(false); - } - }); - - cgb.CreateCommand("are you real") - .Description("Useless.") - .Do(async e => - { - await e.Channel.SendMessage(e.User.Mention + " I will be soon.").ConfigureAwait(false); - }); - - cgb.CreateCommand("are you there") - .Description("Checks if Nadeko is operational.") - .Alias("!", "?") - .Do(SayYes()); - - cgb.CreateCommand("draw") - .Description("Nadeko instructs you to type $draw. Gambling functions start with $") - .Do(async e => - { - await e.Channel.SendMessage("Sorry, I don't gamble, type $draw for that function.").ConfigureAwait(false); - }); cgb.CreateCommand("fire") - .Description("Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire.\n**Usage**: @NadekoBot fire [x]") + .Description("Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire.\n**Usage**: @Wiz-Bot fire [x]") .Parameter("times", ParameterType.Optional) .Do(async e => { @@ -322,7 +185,7 @@ await e.Channel.SendMessage( }); cgb.CreateCommand("rip") - .Description("Shows a grave image of someone with a start year\n**Usage**: @NadekoBot rip @Someone 2000") + .Description("Shows a grave image of someone with a start year\n**Usage**: @Wiz-Bot rip @Someone 2000") .Parameter("user", ParameterType.Required) .Parameter("year", ParameterType.Optional) .Do(async e => @@ -338,7 +201,7 @@ await e.Channel.SendFile("ripzor_m8.png", : e.GetArg("year"))) .ConfigureAwait(false); }); - if (!NadekoBot.Config.DontJoinServers) + if (!WizBot.Config.DontJoinServers) { cgb.CreateCommand("j") .Description("Joins a server using a code.") @@ -363,77 +226,56 @@ await e.Channel.SendFile("ripzor_m8.png", }); } - cgb.CreateCommand("slm") - .Description("Shows the message where you were last mentioned in this channel (checks last 10k messages)") - .Do(async e => - { - - Message msg = null; - var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)) - .Where(m => m.MentionedUsers.Contains(e.User)) - .OrderByDescending(m => m.Timestamp); - if (msgs.Any()) - msg = msgs.First(); - else + cgb.CreateCommand("slm") + .Description("Shows the message where you were last mentioned in this channel (checks last 10k messages)") + .Do(async e => { - var attempt = 0; - Message lastMessage = null; - while (msg == null && attempt++ < 5) + + Message msg = null; + var msgs = (await e.Channel.DownloadMessages(100).ConfigureAwait(false)) + .Where(m => m.MentionedUsers.Contains(e.User)) + .OrderByDescending(m => m.Timestamp); + if (msgs.Any()) + msg = msgs.First(); + else { - var msgsarr = await e.Channel.DownloadMessages(100, lastMessage?.Id).ConfigureAwait(false); - msg = msgsarr - .Where(m => m.MentionedUsers.Contains(e.User)) - .OrderByDescending(m => m.Timestamp) - .FirstOrDefault(); - lastMessage = msgsarr.OrderBy(m => m.Timestamp).First(); + var attempt = 0; + Message lastMessage = null; + while (msg == null && attempt++ < 5) + { + var msgsarr = await e.Channel.DownloadMessages(100, lastMessage?.Id).ConfigureAwait(false); + msg = msgsarr + .Where(m => m.MentionedUsers.Contains(e.User)) + .OrderByDescending(m => m.Timestamp) + .FirstOrDefault(); + lastMessage = msgsarr.OrderBy(m => m.Timestamp).First(); + } } - } - if (msg != null) - await e.Channel.SendMessage($"Last message mentioning you was at {msg.Timestamp}\n**Message from {msg.User.Name}:** {msg.RawText}") - .ConfigureAwait(false); - else - await e.Channel.SendMessage("I can't find a message mentioning you.").ConfigureAwait(false); - }); - - cgb.CreateCommand("bb") - .Description("Says bye to someone.\n**Usage**: @NadekoBot bb @X") - .Parameter("ppl", ParameterType.Unparsed) - .Do(async e => - { - var str = "Bye"; - foreach (var u in e.Message.MentionedUsers) - { - if (u.Id != NadekoBot.Client.CurrentUser.Id) - str += " " + u.Mention; - } - await e.Channel.SendMessage(str).ConfigureAwait(false); - }); + if (msg != null) + await e.Channel.SendMessage($"Last message mentioning you was at {msg.Timestamp}\n**Message from {msg.User.Name}:** {msg.RawText}") + .ConfigureAwait(false); + else + await e.Channel.SendMessage("I can't find a message mentioning you.").ConfigureAwait(false); + }); - cgb.CreateCommand("call") - .Description("Useless. Writes calling @X to chat.\n**Usage**: @NadekoBot call @X ") - .Parameter("who", ParameterType.Required) - .Do(async e => - { - await e.Channel.SendMessage("Calling " + e.Args[0] + "...").ConfigureAwait(false); - }); cgb.CreateCommand("hide") - .Description("Hides Nadeko in plain sight!11!!") + .Description("Hides WizBot in plain sight!11!!") .Do(async e => { using (var ms = Resources.hidden.ToStream(ImageFormat.Png)) { - await client.CurrentUser.Edit(NadekoBot.Creds.Password, avatar: ms).ConfigureAwait(false); + await client.CurrentUser.Edit(WizBot.Creds.Password, avatar: ms).ConfigureAwait(false); } await e.Channel.SendMessage("*hides*").ConfigureAwait(false); }); cgb.CreateCommand("unhide") - .Description("Unhides Nadeko in plain sight!1!!1") + .Description("Unhides WizBot in plain sight!1!!1") .Do(async e => { using (var fs = new FileStream("data/avatar.png", FileMode.Open)) { - await client.CurrentUser.Edit(NadekoBot.Creds.Password, avatar: fs).ConfigureAwait(false); + await client.CurrentUser.Edit(WizBot.Creds.Password, avatar: fs).ConfigureAwait(false); } await e.Channel.SendMessage("*unhides*").ConfigureAwait(false); }); @@ -442,7 +284,7 @@ await e.Channel.SendMessage($"Last message mentioning you was at {msg.Timestamp} .Description("Dumps all of the invites it can to dump.txt.** Owner Only.**") .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; + if (!WizBot.IsOwner(e.User.Id)) return; var i = 0; var j = 0; var invites = ""; @@ -479,7 +321,8 @@ await e.Channel.SendMessage($"Last message mentioning you was at {msg.Timestamp} await e.Channel.SendMessage(construct).ConfigureAwait(false); }); - cgb.CreateCommand("av").Alias("avatar") + cgb.CreateCommand("av") + .Alias("avatar") .Parameter("mention", ParameterType.Required) .Description("Shows a mentioned person's avatar.\n**Usage**: ~av @X") .Do(async e => @@ -522,4 +365,4 @@ public Stream RipName(string name, string year = null) private static Func SayYes() => async e => await e.Channel.SendMessage("Yes. :)").ConfigureAwait(false); } -} +} \ No newline at end of file diff --git a/WizBot/Modules/CustomReactions/CustomReactions.cs b/WizBot/Modules/CustomReactions/CustomReactions.cs new file mode 100644 index 000000000..69617062e --- /dev/null +++ b/WizBot/Modules/CustomReactions/CustomReactions.cs @@ -0,0 +1,58 @@ +using Discord.Commands; +using Discord.Modules; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace WizBot.Modules.CustomReactions +{ + class CustomReactionsModule : DiscordModule + { + public override string Prefix { get; } = ""; + + Random rng = new Random(); + + private Dictionary> commandFuncs; + + public CustomReactionsModule() + { + commandFuncs = new Dictionary> + { + {"%rng%", (e) => rng.Next().ToString()}, + {"%mention%", (e) => WizBot.BotMention }, + {"%user%", e => e.User.Mention }, + {"%target%", e => e.GetArg("args")?.Trim() ?? "" }, + }; + } + + public override void Install(ModuleManager manager) + { + + manager.CreateCommands("", cgb => + { + + cgb.AddCheck(PermissionChecker.Instance); + + foreach (var command in WizBot.Config.CustomReactions) + { + var commandName = command.Key.Replace("%mention%", WizBot.BotMention); + + var c = cgb.CreateCommand(commandName); + if (commandName.Contains(WizBot.BotMention)) + c.Alias(commandName.Replace("<@", "<@!")); + c.Description($"Custom reaction.\n**Usage**:{command.Key}") + .Parameter("args", ParameterType.Unparsed) + .Do(async e => + { + string str = command.Value[rng.Next(0, command.Value.Count())]; + commandFuncs.Keys.ForEach(k => str = str.Replace(k, commandFuncs[k](e))); + await e.Channel.SendMessage(str).ConfigureAwait(false); + }); + } + + }); + } + } +} \ No newline at end of file diff --git a/NadekoBot/Modules/DiscordCommand.cs b/WizBot/Modules/DiscordCommand.cs similarity index 94% rename from NadekoBot/Modules/DiscordCommand.cs rename to WizBot/Modules/DiscordCommand.cs index c6fa8b781..fde5aec75 100644 --- a/NadekoBot/Modules/DiscordCommand.cs +++ b/WizBot/Modules/DiscordCommand.cs @@ -1,7 +1,7 @@ using Discord.Commands; -using NadekoBot.Modules; +using WizBot.Modules; -namespace NadekoBot.Classes +namespace WizBot.Classes { /// /// Base DiscordCommand Class. diff --git a/NadekoBot/Modules/DiscordModule.cs b/WizBot/Modules/DiscordModule.cs similarity index 85% rename from NadekoBot/Modules/DiscordModule.cs rename to WizBot/Modules/DiscordModule.cs index 484277321..57962978d 100644 --- a/NadekoBot/Modules/DiscordModule.cs +++ b/WizBot/Modules/DiscordModule.cs @@ -1,8 +1,8 @@ using Discord.Modules; using System.Collections.Generic; -using NadekoBot.Classes; +using WizBot.Classes; -namespace NadekoBot.Modules { +namespace WizBot.Modules { internal abstract class DiscordModule : IModule { protected readonly HashSet commands = new HashSet(); diff --git a/NadekoBot/Modules/Gambling/DiceRollCommand.cs b/WizBot/Modules/Gambling/DiceRollCommand.cs similarity index 98% rename from NadekoBot/Modules/Gambling/DiceRollCommand.cs rename to WizBot/Modules/Gambling/DiceRollCommand.cs index 2724d3ab6..c78996c6b 100644 --- a/NadekoBot/Modules/Gambling/DiceRollCommand.cs +++ b/WizBot/Modules/Gambling/DiceRollCommand.cs @@ -1,6 +1,6 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; +using WizBot.Classes; +using WizBot.Extensions; using System; using System.Collections.Generic; using System.Drawing; @@ -9,7 +9,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; -namespace NadekoBot.Modules.Gambling +namespace WizBot.Modules.Gambling { internal class DiceRollCommand : DiscordCommand { diff --git a/NadekoBot/Modules/Gambling/DrawCommand.cs b/WizBot/Modules/Gambling/DrawCommand.cs similarity index 96% rename from NadekoBot/Modules/Gambling/DrawCommand.cs rename to WizBot/Modules/Gambling/DrawCommand.cs index 2d3116ee0..bc41eb881 100644 --- a/NadekoBot/Modules/Gambling/DrawCommand.cs +++ b/WizBot/Modules/Gambling/DrawCommand.cs @@ -1,14 +1,14 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Gambling.Helpers; +using WizBot.Classes; +using WizBot.Extensions; +using WizBot.Modules.Gambling.Helpers; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; using System.Threading.Tasks; -namespace NadekoBot.Modules.Gambling +namespace WizBot.Modules.Gambling { internal class DrawCommand : DiscordCommand { diff --git a/NadekoBot/Modules/Gambling/FlipCoinCommand.cs b/WizBot/Modules/Gambling/FlipCoinCommand.cs similarity index 95% rename from NadekoBot/Modules/Gambling/FlipCoinCommand.cs rename to WizBot/Modules/Gambling/FlipCoinCommand.cs index 5c23cd5b2..97797461a 100644 --- a/NadekoBot/Modules/Gambling/FlipCoinCommand.cs +++ b/WizBot/Modules/Gambling/FlipCoinCommand.cs @@ -1,11 +1,11 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; +using WizBot.Classes; +using WizBot.Extensions; using System; using System.Drawing; using System.Threading.Tasks; -namespace NadekoBot.Modules.Gambling +namespace WizBot.Modules.Gambling { internal class FlipCoinCommand : DiscordCommand { diff --git a/NadekoBot/Modules/Gambling/GamblingModule.cs b/WizBot/Modules/Gambling/GamblingModule.cs similarity index 65% rename from NadekoBot/Modules/Gambling/GamblingModule.cs rename to WizBot/Modules/Gambling/GamblingModule.cs index ae8c1cf0b..c81f23517 100644 --- a/NadekoBot/Modules/Gambling/GamblingModule.cs +++ b/WizBot/Modules/Gambling/GamblingModule.cs @@ -1,14 +1,15 @@ using Discord; using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.DataModels; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; using System; using System.Linq; -using System.Threading.Tasks; +using System.Text; -namespace NadekoBot.Modules.Gambling +namespace WizBot.Modules.Gambling { internal class GamblingModule : DiscordModule { @@ -20,7 +21,7 @@ public GamblingModule() commands.Add(new DiceRollCommand(this)); } - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Gambling; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Gambling; public override void Install(ModuleManager manager) { @@ -33,14 +34,35 @@ public override void Install(ModuleManager manager) cgb.CreateCommand(Prefix + "raffle") .Description("Prints a name and ID of a random user from the online list from the (optional) role.") .Parameter("role", ParameterType.Optional) - .Do(RaffleFunc()); + .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 you have.", NadekoBot.Config.CurrencyName)) - .Do(NadekoFlowerCheckFunc()); + cgb.CreateCommand(Prefix + "$$") + .Description(string.Format("Check how much {0}s a person has. (Defaults to yourself)\n**Usage**:`{1}$$` or `{1}$$ @Someone`", + WizBot.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} {WizBot.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)) + cgb.CreateCommand(Prefix + "give") + .Description(string.Format("Give someone a certain amount of {0}s", WizBot.Config.CurrencyName)) .Parameter("amount", ParameterType.Required) .Parameter("receiver", ParameterType.Unparsed) .Do(async e => @@ -51,7 +73,7 @@ public override void Install(ModuleManager manager) return; var mentionedUser = e.Message.MentionedUsers.FirstOrDefault(u => - u.Id != NadekoBot.Client.CurrentUser.Id && + u.Id != WizBot.Client.CurrentUser.Id && u.Id != e.User.Id); if (mentionedUser == null) return; @@ -60,14 +82,14 @@ public override void Install(ModuleManager manager) if (userFlowers < amount) { - await e.Channel.SendMessage($"{e.User.Mention} You don't have enough {NadekoBot.Config.CurrencyName}s. You have only {userFlowers}{NadekoBot.Config.CurrencySign}.").ConfigureAwait(false); + await e.Channel.SendMessage($"{e.User.Mention} You don't have enough {WizBot.Config.CurrencyName}s. You have only {userFlowers}{WizBot.Config.CurrencySign}.").ConfigureAwait(false); return; } FlowersHandler.RemoveFlowers(e.User, "Gift", (int)amount); 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); + await e.Channel.SendMessage($"{e.User.Mention} successfully sent {amount} {WizBot.Config.CurrencyName}s to {mentionedUser.Mention}!").ConfigureAwait(false); }); @@ -84,13 +106,13 @@ public override void Install(ModuleManager manager) return; var mentionedUser = e.Message.MentionedUsers.FirstOrDefault(u => - u.Id != NadekoBot.Client.CurrentUser.Id); + u.Id != WizBot.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); + await e.Channel.SendMessage($"{e.User.Mention} successfully awarded {amount} {WizBot.Config.CurrencyName}s to {mentionedUser.Mention}!").ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "take") @@ -106,50 +128,39 @@ public override void Install(ModuleManager manager) return; var mentionedUser = e.Message.MentionedUsers.FirstOrDefault(u => - u.Id != NadekoBot.Client.CurrentUser.Id); + u.Id != WizBot.Client.CurrentUser.Id); if (mentionedUser == null) return; FlowersHandler.RemoveFlowers(mentionedUser, $"Taken by bot owner.({e.User.Name}/{e.User.Id})", (int)amount); - await e.Channel.SendMessage($"{e.User.Mention} successfully took {amount} {NadekoBot.Config.CurrencyName}s from {mentionedUser.Mention}!").ConfigureAwait(false); + await e.Channel.SendMessage($"{e.User.Mention} successfully took {amount} {WizBot.Config.CurrencyName}s from {mentionedUser.Mention}!").ConfigureAwait(false); }); - }); - } - private static Func NadekoFlowerCheckFunc() - { - return async e => - { - var pts = GetUserFlowers(e.User.Id); - var str = $"`You have {pts} {NadekoBot.Config.CurrencyName}s".SnPl((int)pts) + "`\n"; - for (var i = 0; i < pts; i++) - { - str += NadekoBot.Config.CurrencySign; - } - await e.Channel.SendMessage(str).ConfigureAwait(false); - }; + cgb.CreateCommand(Prefix + "leaderboard") + .Alias(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( + $@"┣━━━━━━━━━━━━━━━━━━━╋━━━━━━━┫ +┃{cs.UserId,-18} ┃ {cs.Value,5} ┃") + ).ToString() + "┗━━━━━━━━━━━━━━━━━━━┻━━━━━━━┛```"); + }); + }); } private static long GetUserFlowers(ulong userId) => Classes.DbHandler.Instance.GetStateByUserId((long)userId)?.Value ?? 0; - private static Func RaffleFunc() - { - return 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); - }; - } } } diff --git a/NadekoBot/Modules/Gambling/Helpers/Cards.cs b/WizBot/Modules/Gambling/Helpers/Cards.cs similarity index 99% rename from NadekoBot/Modules/Gambling/Helpers/Cards.cs rename to WizBot/Modules/Gambling/Helpers/Cards.cs index 76dc73a59..6b164f0da 100644 --- a/NadekoBot/Modules/Gambling/Helpers/Cards.cs +++ b/WizBot/Modules/Gambling/Helpers/Cards.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -namespace NadekoBot.Modules.Gambling.Helpers +namespace WizBot.Modules.Gambling.Helpers { public class Cards { diff --git a/NadekoBot/Modules/Games/Commands/BetrayGame.cs b/WizBot/Modules/Games/Commands/BetrayGame.cs similarity index 69% rename from NadekoBot/Modules/Games/Commands/BetrayGame.cs rename to WizBot/Modules/Games/Commands/BetrayGame.cs index 0bf410b5a..a7461f52b 100644 --- a/NadekoBot/Modules/Games/Commands/BetrayGame.cs +++ b/WizBot/Modules/Games/Commands/BetrayGame.cs @@ -1,10 +1,10 @@ using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; using System; using System.Collections.Generic; using System.Threading.Tasks; -namespace NadekoBot.Modules.Games.Commands +namespace WizBot.Modules.Games.Commands { class BetrayGame : DiscordCommand { @@ -18,18 +18,18 @@ private enum Answers 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.") + .Description("BETRAY GAME. Betray Wiz Bot next turn." + + "If Wiz Bot cooperates - you get extra points, Wiz Bot loses a LOT." + + "If Wiz Bot betrays - you both lose some points.") .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.") + .Description("BETRAY GAME. Cooperate with Wiz Bot next turn." + + "If Wiz Bot cooperates - you both get bonus points." + + "If Wiz Bot betrays - you lose A LOT, Wiz Bot gets extra.") .Do(async e => { @@ -47,13 +47,13 @@ private int UserPoints { userPoints = value; } } - private int nadekoPoints = 0; - private int NadekoPoints { - get { return nadekoPoints; } + private int wizPoints = 0; + private int WizPoints { + get { return wizPoints; } set { if (value < 0) - nadekoPoints = 0; - nadekoPoints = value; + wizPoints = 0; + wizPoints = value; } } @@ -62,50 +62,50 @@ private int NadekoPoints { 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.` "; + ? ":no_entry: `You betrayed Wiz Bot` - you monster." + : ":ok: `You cooperated with Wiz Bot.` "; var currentAnswer = NextAnswer; - var nadekoResponse = currentAnswer == Answers.Betray - ? ":no_entry: `aww Nadeko betrayed you` - she is so cute" - : ":ok: `Nadeko cooperated.`"; + var wizResponse = currentAnswer == Answers.Betray + ? ":no_entry: `aww Wiz Bot betrayed you` - she is so cute" + : ":ok: `Wiz Bot cooperated.`"; NextAnswer = userAnswer; if (userAnswer == Answers.Betray && currentAnswer == Answers.Betray) { - NadekoPoints--; + WizPoints--; UserPoints--; } else if (userAnswer == Answers.Cooperate && currentAnswer == Answers.Cooperate) { - NadekoPoints += 2; + WizPoints += 2; UserPoints += 2; } else if (userAnswer == Answers.Betray && currentAnswer == Answers.Cooperate) { - NadekoPoints -= 3; + WizPoints -= 3; UserPoints += 3; } else if (userAnswer == Answers.Cooperate && currentAnswer == Answers.Betray) { - NadekoPoints += 3; + WizPoints += 3; UserPoints -= 3; } await e.Channel.SendMessage($"**ROUND {++round}**\n" + $"{response}\n" + - $"{nadekoResponse}\n" + + $"{wizResponse}\n" + $"--------------------------------\n" + - $"Nadeko has {NadekoPoints} points." + + $"Wiz Bot has {WizPoints} points." + $"You have {UserPoints} points." + $"--------------------------------\n") .ConfigureAwait(false); if (round < 10) return; - if (nadekoPoints == userPoints) + if (wizPoints == userPoints) await e.Channel.SendMessage("Its a draw").ConfigureAwait(false); - else if (nadekoPoints > userPoints) - await e.Channel.SendMessage("Nadeko won.").ConfigureAwait(false); + else if (wizPoints > userPoints) + await e.Channel.SendMessage("Wiz Bot won.").ConfigureAwait(false); else await e.Channel.SendMessage("You won.").ConfigureAwait(false); - nadekoPoints = 0; + wizPoints = 0; userPoints = 0; round = 0; } diff --git a/NadekoBot/Modules/Games/Commands/Bomberman.cs b/WizBot/Modules/Games/Commands/Bomberman.cs similarity index 93% rename from NadekoBot/Modules/Games/Commands/Bomberman.cs rename to WizBot/Modules/Games/Commands/Bomberman.cs index 4987dd666..a7105c91e 100644 --- a/NadekoBot/Modules/Games/Commands/Bomberman.cs +++ b/WizBot/Modules/Games/Commands/Bomberman.cs @@ -1,11 +1,11 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; using System.Text; using System.Timers; -using static NadekoBot.Modules.Games.Commands.Bomberman; +using static WizBot.Modules.Games.Commands.Bomberman; -namespace NadekoBot.Modules.Games.Commands +namespace WizBot.Modules.Games.Commands { class Bomberman : DiscordCommand { @@ -30,10 +30,10 @@ public Bomberman(DiscordModule module) : base(module) board[i, j] = new Field(); } } - NadekoBot.Client.MessageReceived += (s, e) => + WizBot.Client.MessageReceived += (s, e) => { if (e.Channel != gameChannel || - e.User.Id == NadekoBot.Client.CurrentUser.Id) + e.User.Id == WizBot.Client.CurrentUser.Id) return; if (e.Message.Text == "w") diff --git a/NadekoBot/Modules/Games/Commands/Leet.cs b/WizBot/Modules/Games/Commands/Leet.cs similarity index 99% rename from NadekoBot/Modules/Games/Commands/Leet.cs rename to WizBot/Modules/Games/Commands/Leet.cs index 338452997..5c2773fab 100644 --- a/NadekoBot/Modules/Games/Commands/Leet.cs +++ b/WizBot/Modules/Games/Commands/Leet.cs @@ -1,11 +1,11 @@ using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; using System.Text; //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 WizBot.Modules.Games.Commands { internal class Leet : DiscordCommand { diff --git a/NadekoBot/Modules/Games/Commands/PlantPick.cs b/WizBot/Modules/Games/Commands/PlantPick.cs similarity index 87% rename from NadekoBot/Modules/Games/Commands/PlantPick.cs rename to WizBot/Modules/Games/Commands/PlantPick.cs index 86db0d1f7..5a9b5d001 100644 --- a/NadekoBot/Modules/Games/Commands/PlantPick.cs +++ b/WizBot/Modules/Games/Commands/PlantPick.cs @@ -1,13 +1,13 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; using System; using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Threading.Tasks; -namespace NadekoBot.Modules.Games.Commands +namespace WizBot.Modules.Games.Commands { /// /// Flower picking/planting idea is given to me by its @@ -42,7 +42,7 @@ internal override void Init(CommandGroupBuilder cgb) await msg.Delete().ConfigureAwait(false); await FlowersHandler.AddFlowersAsync(e.User, "Picked a flower.", 1, true).ConfigureAwait(false); - msg = await e.Channel.SendMessage($"**{e.User.Name}** picked a {NadekoBot.Config.CurrencyName}!").ConfigureAwait(false); + msg = await e.Channel.SendMessage($"**{e.User.Name}** picked a {WizBot.Config.CurrencyName}!").ConfigureAwait(false); await Task.Delay(10000).ConfigureAwait(false); await msg.Delete().ConfigureAwait(false); }); @@ -55,13 +55,13 @@ internal override void Init(CommandGroupBuilder cgb) { if (plantedFlowerChannels.ContainsKey(e.Channel.Id)) { - e.Channel.SendMessage($"There is already a {NadekoBot.Config.CurrencyName} in this channel."); + e.Channel.SendMessage($"There is already a {WizBot.Config.CurrencyName} in this channel."); return; } var removed = FlowersHandler.RemoveFlowers(e.User, "Planted a flower.", 1); if (!removed) { - e.Channel.SendMessage($"You don't have any {NadekoBot.Config.CurrencyName}s.").Wait(); + e.Channel.SendMessage($"You don't have any {WizBot.Config.CurrencyName}s.").Wait(); return; } @@ -70,13 +70,13 @@ internal override void Init(CommandGroupBuilder cgb) Message msg; //todo send message after, not in lock if (file == null) - msg = e.Channel.SendMessage(NadekoBot.Config.CurrencySign).GetAwaiter().GetResult(); + msg = e.Channel.SendMessage(WizBot.Config.CurrencySign).GetAwaiter().GetResult(); else msg = e.Channel.SendFile(file).GetAwaiter().GetResult(); plantedFlowerChannels.TryAdd(e.Channel.Id, msg); } - 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"); + var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(WizBot.Config.CurrencyName[0]); + var msg2 = await e.Channel.SendMessage($"Oh how Nice! **{e.User.Name}** planted {(vowelFirst ? "an" : "a")} {WizBot.Config.CurrencyName}. Pick it using {Module.Prefix}pick"); await Task.Delay(20000).ConfigureAwait(false); await msg2.Delete().ConfigureAwait(false); }); diff --git a/NadekoBot/Modules/Games/Commands/PollCommand.cs b/WizBot/Modules/Games/Commands/PollCommand.cs similarity index 97% rename from NadekoBot/Modules/Games/Commands/PollCommand.cs rename to WizBot/Modules/Games/Commands/PollCommand.cs index 1e1c7b036..154006ce0 100644 --- a/NadekoBot/Modules/Games/Commands/PollCommand.cs +++ b/WizBot/Modules/Games/Commands/PollCommand.cs @@ -1,6 +1,6 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; -namespace NadekoBot.Modules.Games.Commands +namespace WizBot.Modules.Games.Commands { internal class PollCommand : DiscordCommand { @@ -81,7 +81,7 @@ public Poll(CommandEventArgs e, string question, IEnumerable enumerable) public async Task StartPoll() { started = DateTime.Now; - NadekoBot.Client.MessageReceived += Vote; + WizBot.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"; @@ -93,7 +93,7 @@ public async Task StartPoll() public async Task StopPoll(Channel ch) { - NadekoBot.Client.MessageReceived -= Vote; + WizBot.Client.MessageReceived -= Vote; Poll throwaway; PollCommand.ActivePolls.TryRemove(e.Server, out throwaway); try diff --git a/NadekoBot/Modules/Games/Commands/SpeedTyping.cs b/WizBot/Modules/Games/Commands/SpeedTyping.cs similarity index 94% rename from NadekoBot/Modules/Games/Commands/SpeedTyping.cs rename to WizBot/Modules/Games/Commands/SpeedTyping.cs index 8a8072085..a50596e0e 100644 --- a/NadekoBot/Modules/Games/Commands/SpeedTyping.cs +++ b/WizBot/Modules/Games/Commands/SpeedTyping.cs @@ -1,8 +1,8 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.DataModels; -using NadekoBot.Extensions; +using WizBot.Classes; +using WizBot.DataModels; +using WizBot.Extensions; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -10,7 +10,7 @@ using System.Linq; using System.Threading.Tasks; -namespace NadekoBot.Modules.Games.Commands +namespace WizBot.Modules.Games.Commands { public static class SentencesProvider @@ -51,7 +51,7 @@ public TypingGame(Channel channel) internal async Task Stop() { if (!IsActive) return false; - NadekoBot.Client.MessageReceived -= AnswerReceived; + WizBot.Client.MessageReceived -= AnswerReceived; finishedUserIds.Clear(); IsActive = false; sw.Stop(); @@ -95,14 +95,14 @@ internal async Task Start() private void HandleAnswers() { - NadekoBot.Client.MessageReceived += AnswerReceived; + WizBot.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; + if (e.Channel == null || e.Channel.Id != channel.Id || e.User.Id == WizBot.Client.CurrentUser.Id) return; var guess = e.Message.RawText; @@ -180,7 +180,7 @@ internal override void Init(CommandGroupBuilder cgb) .Parameter("text", ParameterType.Unparsed) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("text"))) return; + if (!WizBot.IsOwner(e.User.Id) || string.IsNullOrWhiteSpace(e.GetArg("text"))) return; DbHandler.Instance.InsertData(new TypingArticle { diff --git a/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs b/WizBot/Modules/Games/Commands/Trivia/TriviaGame.cs similarity index 92% rename from NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs rename to WizBot/Modules/Games/Commands/Trivia/TriviaGame.cs index 2865b1b20..21521f528 100644 --- a/NadekoBot/Modules/Games/Commands/Trivia/TriviaGame.cs +++ b/WizBot/Modules/Games/Commands/Trivia/TriviaGame.cs @@ -1,7 +1,7 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; +using WizBot.Classes; +using WizBot.Extensions; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; -namespace NadekoBot.Modules.Games.Commands.Trivia +namespace WizBot.Modules.Games.Commands.Trivia { internal class TriviaGame { @@ -34,11 +34,12 @@ internal class TriviaGame public int WinRequirement { get; } = 10; - public TriviaGame(CommandEventArgs e, bool showHints) + public TriviaGame(CommandEventArgs e, bool showHints, int winReq = 10) { ShowHints = showHints; server = e.Server; channel = e.Channel; + WinRequirement = winReq; Task.Run(StartGame); } @@ -62,7 +63,7 @@ private async Task StartGame() await channel.SendMessage($":question: **{CurrentQuestion.Question}**").ConfigureAwait(false); //receive messages - NadekoBot.Client.MessageReceived += PotentialGuess; + WizBot.Client.MessageReceived += PotentialGuess; //allow people to guess GameActive = true; @@ -85,7 +86,7 @@ private async Task StartGame() 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; + WizBot.Client.MessageReceived -= PotentialGuess; // load next question if game is still running await Task.Delay(2000).ConfigureAwait(false); } @@ -113,7 +114,7 @@ private async void PotentialGuess(object sender, MessageEventArgs e) { if (e.Channel.IsPrivate) return; if (e.Server != server) return; - if (e.User.Id == NadekoBot.Client.CurrentUser.Id) return; + if (e.User.Id == WizBot.Client.CurrentUser.Id) return; var guess = false; lock (_guessLock) @@ -130,7 +131,7 @@ private async void PotentialGuess(object sender, MessageEventArgs e) 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! Its {e.User.Mention}.").ConfigureAwait(false); + 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); } diff --git a/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs b/WizBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs similarity index 97% rename from NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs rename to WizBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs index 9bfa2da49..b2cb4751b 100644 --- a/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs +++ b/WizBot/Modules/Games/Commands/Trivia/TriviaQuestion.cs @@ -1,10 +1,10 @@ -using NadekoBot.Extensions; +using WizBot.Extensions; using System; using System.Collections.Generic; using System.Text.RegularExpressions; // THANKS @ShoMinamimoto for suggestions and coding help -namespace NadekoBot.Modules.Games.Commands.Trivia +namespace WizBot.Modules.Games.Commands.Trivia { public class TriviaQuestion { diff --git a/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs b/WizBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs similarity index 96% rename from NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs rename to WizBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs index 3aa09a560..a927617a2 100644 --- a/NadekoBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs +++ b/WizBot/Modules/Games/Commands/Trivia/TriviaQuestionPool.cs @@ -4,7 +4,7 @@ using System.IO; using System.Linq; -namespace NadekoBot.Modules.Games.Commands.Trivia +namespace WizBot.Modules.Games.Commands.Trivia { public class TriviaQuestionPool { diff --git a/NadekoBot/Modules/Games/Commands/TriviaCommand.cs b/WizBot/Modules/Games/Commands/TriviaCommand.cs similarity index 75% rename from NadekoBot/Modules/Games/Commands/TriviaCommand.cs rename to WizBot/Modules/Games/Commands/TriviaCommand.cs index 15d29e07d..dc1d432fe 100644 --- a/NadekoBot/Modules/Games/Commands/TriviaCommand.cs +++ b/WizBot/Modules/Games/Commands/TriviaCommand.cs @@ -1,10 +1,11 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Games.Commands.Trivia; +using WizBot.Classes; +using WizBot.Modules.Games.Commands.Trivia; +using System; using System.Collections.Concurrent; using System.Linq; -namespace NadekoBot.Modules.Games.Commands +namespace WizBot.Modules.Games.Commands { internal class TriviaCommands : DiscordCommand { @@ -18,8 +19,8 @@ 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. 30 seconds per question." + - $"\n**Usage**:`{Module.Prefix}t nohint`") + "First player to get to 10 points wins by default. You can specify a different number. 30 seconds per question." + + $"\n**Usage**:`{Module.Prefix}t nohint` or `{Module.Prefix}t 5 nohint`") .Parameter("args", ParameterType.Multiple) .Do(async e => { @@ -27,9 +28,16 @@ internal override void Init(CommandGroupBuilder cgb) if (!RunningTrivias.TryGetValue(e.Server.Id, out trivia)) { var showHints = !e.Args.Contains("nohint"); - var triviaGame = new TriviaGame(e, showHints); - if (RunningTrivias.TryAdd(e.Server.Id, triviaGame)) - await e.Channel.SendMessage("**Trivia game started!**").ConfigureAwait(false); + 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 < 0) + 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); } diff --git a/NadekoBot/Modules/Games/GamesModule.cs b/WizBot/Modules/Games/GamesModule.cs similarity index 85% rename from NadekoBot/Modules/Games/GamesModule.cs rename to WizBot/Modules/Games/GamesModule.cs index 8eaa1244b..26339a674 100644 --- a/NadekoBot/Modules/Games/GamesModule.cs +++ b/WizBot/Modules/Games/GamesModule.cs @@ -1,12 +1,12 @@ using Discord.Commands; using Discord.Modules; -using NadekoBot.Extensions; -using NadekoBot.Modules.Games.Commands; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Extensions; +using WizBot.Modules.Games.Commands; +using WizBot.Modules.Permissions.Classes; using System; using System.Linq; -namespace NadekoBot.Modules.Games +namespace WizBot.Modules.Games { internal class GamesModule : DiscordModule { @@ -24,7 +24,7 @@ public GamesModule() } - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Games; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Games; public override void Install(ModuleManager manager) { @@ -60,14 +60,14 @@ public override void Install(ModuleManager manager) try { await e.Channel.SendMessage( - $":question: **Question**: `{question}` \n🎱 **8Ball Answers**: `{NadekoBot.Config._8BallResponses[rng.Next(0, NadekoBot.Config._8BallResponses.Length)]}`") + $":question: **Question**: `{question}` \n🎱 **8Ball Answers**: `{WizBot.Config._8BallResponses[rng.Next(0, WizBot.Config._8BallResponses.Length)]}`") .ConfigureAwait(false); } catch { } }); cgb.CreateCommand(Prefix + "rps") - .Description("Play a game of rocket paperclip scissors with Nadeko.\n**Usage**: >rps scissors") + .Description("Play a game of rocket paperclip scissors with Wiz Bot.\n**Usage**: >rps scissors") .Parameter("input", ParameterType.Required) .Do(async e => { @@ -92,22 +92,22 @@ await e.Channel.SendMessage( default: return; } - var nadekoPick = new Random().Next(0, 3); + var wizPick = new Random().Next(0, 3); var msg = ""; - if (pick == nadekoPick) + if (pick == wizPick) 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 if ((pick == 0 && wizPick == 1) || + (pick == 1 && wizPick == 2) || + (pick == 2 && wizPick == 0)) + msg = $"{WizBot.BotMention} won! :{GetRPSPick(wizPick)}: beats :{GetRPSPick(pick)}:"; else - msg = $"{e.User.Mention} won! :{GetRPSPick(pick)}: beats :{GetRPSPick(nadekoPick)}:"; + msg = $"{e.User.Mention} won! :{GetRPSPick(pick)}: beats :{GetRPSPick(wizPick)}:"; await e.Channel.SendMessage(msg).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "linux") - .Description("Prints a customizable Linux interjection") + .Description($"Prints a customizable Linux interjection\n**Usage**: `{Prefix}linux Spyware Windows`") .Parameter("gnu", ParameterType.Required) .Parameter("linux", ParameterType.Required) .Do(async e => diff --git a/WizBot/Modules/Help/Commands/HelpCommand.cs b/WizBot/Modules/Help/Commands/HelpCommand.cs new file mode 100644 index 000000000..e979faeb0 --- /dev/null +++ b/WizBot/Modules/Help/Commands/HelpCommand.cs @@ -0,0 +1,118 @@ +using Discord.Commands; +using WizBot.Extensions; +using WizBot.Modules; +using WizBot.Modules.Permissions.Classes; +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +namespace WizBot.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 = WizBot.Client.GetService().AllCommands + .FirstOrDefault(c => c.Text.ToLowerInvariant().Equals(comToFind) || + c.Aliases.Select(a => a.ToLowerInvariant()).Contains(comToFind)); + if (com != null) + await e.Channel.SendMessage($"`Help for '{com.Text}':` {com.Description}").ConfigureAwait(false); + }).ConfigureAwait(false); + }; + public static string HelpString => (WizBot.IsBot + ? $"To add me to your server, use this link** -> \n" + : $"To invite me to your server, just send me an invite link here.") + + $"You can use `{WizBot.Config.CommandPrefixes.Help}modules` command to see a list of all modules.\n" + + $"You can use `{WizBot.Config.CommandPrefixes.Help}commands ModuleName`" + + $" (for example `{WizBot.Config.CommandPrefixes.Help}commands Administration`) to see a list of all of the commands in that module.\n" + + $"For a specific command help, use `{WizBot.Config.CommandPrefixes.Help}h \"Command name\"` (for example `-h \"!m q\"`)\n" + + "**LIST OF COMMANDS CAN BE FOUND ON THIS LINK**\n\n "; + + public static string DMHelpString => WizBot.Config.DMHelpString; + + public Action DoGitFunc() => e => + { + string helpstr = +$@"######For more information, go to: **http://wizkiller96network.com/** +######You can donate on paypal: `inick01@live.com` +#WizBot List Of Commands +Version: `{WizStats.Instance.BotVersion}`"; + + + string lastCategory = ""; + foreach (var com in WizBot.Client.GetService().AllCommands) + { + if (com.Category != lastCategory) + { + helpstr += "\n### " + com.Category + " \n"; + helpstr += "Command and aliases | Description | Usage\n"; + helpstr += "----------------|--------------|-------\n"; + lastCategory = com.Category; + } + helpstr += PrintCommandHelp(com); + } + helpstr = helpstr.Replace(WizBot.BotMention, "@BotName"); + helpstr = helpstr.Replace("\n**Usage**:", " | ").Replace("**Usage**:", " | ").Replace("**Description:**", " | ").Replace("\n|", " | \n"); +#if DEBUG + File.WriteAllText("../../../commandlist.md", helpstr); +#else + File.WriteAllText("commandlist.md", helpstr); +#endif + }; + + internal override void Init(CommandGroupBuilder cgb) + { + cgb.CreateCommand(Module.Prefix + "h") + .Alias(Module.Prefix + "help", WizBot.BotMention + " help", WizBot.BotMention + " h", "~h") + .Description("Either shows a help for a single command, or PMs you help link if no arguments are specified.\n**Usage**: '-h !m q' or just '-h' ") + .Parameter("command", ParameterType.Unparsed) + .Do(HelpFunc()); + cgb.CreateCommand(Module.Prefix + "hgit") + .Description("Generates the commandlist.md file. **Owner Only!**") + .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.") + .Do(async e => + await e.Channel.SendMessage( +@"**FULL README**: +**GUIDE ONLY**: +**WINDOWS SETUP GUIDE**: +**LINUX SETUP GUIDE**: +**LIST OF COMMANDS**: ").ConfigureAwait(false)); + + cgb.CreateCommand(Module.Prefix + "donate") + .Alias("~donate") + .Description("Instructions for helping the project!") + .Do(async e => + { + await e.Channel.SendMessage( +$@"I've created a **paypal** email for WizBot, so if you wish to support the project, you can send your donations to `inick01@live.com` +Don't forget to leave your discord name or id in the message, so that I can reward people who help out. +You can join WizBot server by typing {Module.Prefix}h and you will get an invite in a private message. +*If you want to support in some other way or on a different platform, please message me*" + ).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) { } + } +} \ No newline at end of file diff --git a/NadekoBot/Modules/Help/HelpModule.cs b/WizBot/Modules/Help/HelpModule.cs similarity index 80% rename from NadekoBot/Modules/Help/HelpModule.cs rename to WizBot/Modules/Help/HelpModule.cs index 593fbd5fd..3267a9435 100644 --- a/NadekoBot/Modules/Help/HelpModule.cs +++ b/WizBot/Modules/Help/HelpModule.cs @@ -1,11 +1,11 @@ using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes.Help.Commands; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes.Help.Commands; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; using System.Linq; -namespace NadekoBot.Modules.Help +namespace WizBot.Modules.Help { internal class HelpModule : DiscordModule { @@ -15,7 +15,7 @@ public HelpModule() commands.Add(new HelpCommand(this)); } - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Help; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Help; public override void Install(ModuleManager manager) { @@ -29,7 +29,7 @@ public override void Install(ModuleManager manager) .Description("List all bot modules.") .Do(async e => { - await e.Channel.SendMessage("`List of modules:` \n• " + string.Join("\n• ", NadekoBot.Client.GetService().Modules.Select(m => m.Name))) + await e.Channel.SendMessage("`List of modules:` \n• " + string.Join("\n• ", WizBot.Client.GetService().Modules.Select(m => m.Name))) .ConfigureAwait(false); }); @@ -39,7 +39,7 @@ await e.Channel.SendMessage("`List of modules:` \n• " + string.Join("\n• ", .Parameter("module", ParameterType.Unparsed) .Do(async e => { - var cmds = NadekoBot.Client.GetService().AllCommands + var cmds = WizBot.Client.GetService().AllCommands .Where(c => c.Category.ToLower() == e.GetArg("module").Trim().ToLower()); var cmdsArray = cmds as Command[] ?? cmds.ToArray(); if (!cmdsArray.Any()) diff --git a/NadekoBot/Modules/Music/Classes/MusicControls.cs b/WizBot/Modules/Music/Classes/MusicControls.cs similarity index 94% rename from NadekoBot/Modules/Music/Classes/MusicControls.cs rename to WizBot/Modules/Music/Classes/MusicControls.cs index 4833dda83..ef546e09f 100644 --- a/NadekoBot/Modules/Music/Classes/MusicControls.cs +++ b/WizBot/Modules/Music/Classes/MusicControls.cs @@ -1,11 +1,11 @@ using Discord; using Discord.Audio; -using NadekoBot.Extensions; +using WizBot.Extensions; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -namespace NadekoBot.Modules.Music.Classes +namespace WizBot.Modules.Music.Classes { public enum MusicType @@ -95,10 +95,12 @@ public MusicPlayer(Channel startingVoiceChannel, float? defaultVolume) Console.WriteLine($"Exception in PlaySong: {ex}"); } OnCompleted(this, curSong); - if (RepeatSong) - playlist.Insert(0, curSong); - else if (RepeatPlaylist) - playlist.Insert(playlist.Count, curSong); + curSong = CurrentSong; //to check if its null now + if (curSong != null) + if (RepeatSong) + playlist.Insert(0, curSong); + else if (RepeatPlaylist) + playlist.Insert(playlist.Count, curSong); SongCancelSource = new CancellationTokenSource(); cancelToken = SongCancelSource.Token; } diff --git a/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs b/WizBot/Modules/Music/Classes/PoopyBuffer.cs similarity index 99% rename from NadekoBot/Modules/Music/Classes/PoopyBuffer.cs rename to WizBot/Modules/Music/Classes/PoopyBuffer.cs index 15dc49770..2d81600e7 100644 --- a/NadekoBot/Modules/Music/Classes/PoopyBuffer.cs +++ b/WizBot/Modules/Music/Classes/PoopyBuffer.cs @@ -2,7 +2,7 @@ using System.Threading; using System.Threading.Tasks; -namespace NadekoBot.Modules.Music.Classes +namespace WizBot.Modules.Music.Classes { /// diff --git a/NadekoBot/Modules/Music/Classes/Song.cs b/WizBot/Modules/Music/Classes/Song.cs similarity index 96% rename from NadekoBot/Modules/Music/Classes/Song.cs rename to WizBot/Modules/Music/Classes/Song.cs index 6ebf97d2c..a5ff9efba 100644 --- a/NadekoBot/Modules/Music/Classes/Song.cs +++ b/WizBot/Modules/Music/Classes/Song.cs @@ -1,6 +1,6 @@ using Discord.Audio; -using NadekoBot.Classes; -using NadekoBot.Extensions; +using WizBot.Classes; +using WizBot.Extensions; using System; using System.Diagnostics; using System.IO; @@ -10,7 +10,7 @@ using System.Threading.Tasks; using VideoLibrary; -namespace NadekoBot.Modules.Music.Classes +namespace WizBot.Modules.Music.Classes { public class SongInfo { @@ -249,7 +249,7 @@ public static async Task ResolveSong(string query, MusicType musicType = M }); } var link = await SearchHelper.FindYoutubeUrlByKeywords(query).ConfigureAwait(false); - if (link == String.Empty) + 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 videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio); @@ -260,7 +260,11 @@ public static async Task ResolveSong(string query, MusicType musicType = M if (video == null) // do something with this error throw new Exception("Could not load any video elements based on the query."); - return new Song(new SongInfo + var m = Regex.Match(query, @"\?t=(?\d*)"); + int gotoTime = 0; + if (m.Captures.Count > 0) + 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" Provider = "YouTube", @@ -268,6 +272,8 @@ public static async Task ResolveSong(string query, MusicType musicType = M Query = link, ProviderType = musicType, }); + song.SkipTo = gotoTime; + return song; } catch (Exception ex) { diff --git a/NadekoBot/Modules/Music/Classes/SoundCloud.cs b/WizBot/Modules/Music/Classes/SoundCloud.cs similarity index 87% rename from NadekoBot/Modules/Music/Classes/SoundCloud.cs rename to WizBot/Modules/Music/Classes/SoundCloud.cs index 00216fbbe..8c57725e7 100644 --- a/NadekoBot/Modules/Music/Classes/SoundCloud.cs +++ b/WizBot/Modules/Music/Classes/SoundCloud.cs @@ -1,8 +1,8 @@ -using NadekoBot.Classes; +using WizBot.Classes; using System; using System.Threading.Tasks; -namespace NadekoBot.Modules.Music.Classes +namespace WizBot.Modules.Music.Classes { public class SoundCloud { @@ -16,10 +16,10 @@ public async Task GetVideoAsync(string url) { 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(WizBot.Creds.SoundCloudClientID)) + throw new ArgumentNullException(nameof(WizBot.Creds.SoundCloudClientID)); - var response = await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.Creds.SoundCloudClientID}").ConfigureAwait(false); + var response = await SearchHelper.GetResponseStringAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={WizBot.Creds.SoundCloudClientID}").ConfigureAwait(false); var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject(response); if (responseObj?.Kind != "track") @@ -40,7 +40,7 @@ public class SoundCloudVideo public string Title = ""; public string FullName => User.Name + " - " + Title; public bool Streamable = false; - 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={WizBot.Creds.SoundCloudClientID}"; } public class SoundCloudUser { diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/WizBot/Modules/Music/MusicModule.cs similarity index 87% rename from NadekoBot/Modules/Music/MusicModule.cs rename to WizBot/Modules/Music/MusicModule.cs index afba2f3ca..d6520c761 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/WizBot/Modules/Music/MusicModule.cs @@ -1,11 +1,11 @@ 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 WizBot.Classes; +using WizBot.DataModels; +using WizBot.Extensions; +using WizBot.Modules.Music.Classes; +using WizBot.Modules.Permissions.Classes; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -13,7 +13,7 @@ using System.Linq; using System.Threading.Tasks; -namespace NadekoBot.Modules.Music +namespace WizBot.Modules.Music { internal class MusicModule : DiscordModule { @@ -24,7 +24,7 @@ internal class MusicModule : DiscordModule public MusicModule() { // ready for 1.0 - //NadekoBot.Client.UserUpdated += (s, e) => + //WizBot.Client.UserUpdated += (s, e) => //{ // try // { @@ -41,11 +41,11 @@ public MusicModule() } - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Music; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Music; public override void Install(ModuleManager manager) { - var client = NadekoBot.Client; + var client = WizBot.Client; manager.CreateCommands(Prefix, cgb => { @@ -57,12 +57,13 @@ public override void Install(ModuleManager manager) cgb.CreateCommand("n") .Alias("next") .Alias("skip") - .Description("Goes to the next song in the queue.**Usage**: `!m n`") + .Description("Goes to the next song in the queue. You have to be in the same voice channel as the bot.\n**Usage**: `!m n`") .Do(e => { MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; - musicPlayer.Next(); + if (musicPlayer.PlaybackVoiceChannel == e.User.VoiceChannel) + musicPlayer.Next(); }); cgb.CreateCommand("s") @@ -74,7 +75,8 @@ await Task.Run(() => { MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; - musicPlayer.Stop(); + if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) + musicPlayer.Stop(); }).ConfigureAwait(false); }); @@ -88,7 +90,8 @@ await Task.Run(() => { MusicPlayer musicPlayer; if (!MusicPlayers.TryRemove(e.Server, out musicPlayer)) return; - musicPlayer.Destroy(); + if (e.User.VoiceChannel == musicPlayer.PlaybackVoiceChannel) + musicPlayer.Destroy(); }).ConfigureAwait(false); }); @@ -100,6 +103,8 @@ await Task.Run(() => MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; musicPlayer.TogglePause(); + if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; if (musicPlayer.Paused) await e.Channel.SendMessage("🎵`Music Player paused.`").ConfigureAwait(false); else @@ -172,6 +177,8 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + 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)) @@ -208,6 +215,8 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; + if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; musicPlayer.SetVolume(0); }); @@ -218,6 +227,8 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; + if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; musicPlayer.SetVolume(100); }); @@ -228,6 +239,8 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + MusicPlayer musicPlayer; if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) return; + if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; musicPlayer.SetVolume(50); }); @@ -238,6 +251,8 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + 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); @@ -249,7 +264,7 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + }); cgb.CreateCommand("pl") - .Description("Queues up to 25 songs from a youtube playlist specified by a link, or keywords.\n**Usage**: `!m pl playlist link or name`") + .Description("Queues up to 50 songs from a youtube playlist specified by a link, or keywords.\n**Usage**: `!m pl playlist link or name`") .Parameter("playlist", ParameterType.Unparsed) .Do(async e => { @@ -261,10 +276,21 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + 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 ids = await SearchHelper.GetVideoIDs(await SearchHelper.GetPlaylistIdByKeyword(arg).ConfigureAwait(false)).ConfigureAwait(false); + 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.`"); + return; + } //todo TEMPORARY SOLUTION, USE RESOLVE QUEUE IN THE FUTURE var idArray = ids as string[] ?? ids.ToArray(); - var count = idArray.Count(); + var count = idArray.Length; var msg = await e.Channel.SendMessage($"🎵 `Attempting to queue {count} songs".SnPl(count) + "...`").ConfigureAwait(false); foreach (var id in idArray) @@ -279,7 +305,7 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + }); cgb.CreateCommand("lopl") - .Description("Queues up to 50 songs from a directory. **Owner Only!**\n**Usage**: `!m lopl C:/music/classical`") + .Description("Queues all songs from a directory. **Owner Only!**\n**Usage**: `!m lopl C:/music/classical`") .Parameter("directory", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -319,7 +345,7 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + }); cgb.CreateCommand("lo") - .Description("Queues a local file by specifying a full path. **Owner Only!**\n**Usage**: `!m ra C:/music/mysong.mp3`") + .Description("Queues a local file by specifying a full path. **Owner Only!**\n**Usage**: `!m lo C:/music/mysong.mp3`") .Parameter("path", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -352,6 +378,8 @@ await e.Channel.SendMessage($"🎵`Now Playing` {currentSong.PrettyName} " + { return; } + if (e.User.VoiceChannel != musicPlayer.PlaybackVoiceChannel) + return; if (arg?.ToLower() == "all") { musicPlayer.ClearQueue(); @@ -471,15 +499,7 @@ await e.Channel.SendMessage(currentValue ? }); - //cgb.CreateCommand("info") - // .Description("Prints music info (queued/finished/playing) only to this channel") - // .Do(async e => - // { - // MusicPlayer musicPlayer; - // if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) - // return; - // musicPlayer - // }); + cgb.CreateCommand("load") .Description("Loads a playlist under a certain name. \n**Usage**: `!m load classical-1`") @@ -536,7 +556,24 @@ await e.Channel.SendMessage(currentValue ? } }); - cgb.CreateCommand("goto") + cgb.CreateCommand("playlists") + .Alias("pls") + .Description("Lists all playlists. Paginated. 20 per page. Default page is 0.\n**Usage**:`!m 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}`"); + 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} ---```"); + }); + + cgb.CreateCommand("goto") .Description("Goes to a specific time in seconds in a song.") .Parameter("time") .Do(async e => @@ -546,6 +583,9 @@ await e.Channel.SendMessage(currentValue ? 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; @@ -571,7 +611,20 @@ await e.Channel.SendMessage(currentValue ? await e.Channel.SendMessage($"`Skipped to {minutes}:{seconds}`").ConfigureAwait(false); }); - }); + cgb.CreateCommand("getlink") + .Alias("gl") + .Description("Shows a link to the currently playing song.") + .Do(async e => + { + MusicPlayer musicPlayer; + if (!MusicPlayers.TryGetValue(e.Server, out musicPlayer)) + return; + var curSong = musicPlayer.CurrentSong; + if (curSong == null) + return; + await e.Channel.SendMessage($"🎶`Current song:` <{curSong.SongInfo.Query}>"); + }); + }); } private async Task QueueSong(Channel textCh, Channel voiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) diff --git a/NadekoBot/Modules/NSFW/NSFWModule.cs b/WizBot/Modules/NSFW/NSFWModule.cs similarity index 68% rename from NadekoBot/Modules/NSFW/NSFWModule.cs rename to WizBot/Modules/NSFW/NSFWModule.cs index d75ad5316..5beb02caa 100644 --- a/NadekoBot/Modules/NSFW/NSFWModule.cs +++ b/WizBot/Modules/NSFW/NSFWModule.cs @@ -1,18 +1,18 @@ using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; using Newtonsoft.Json.Linq; using System; -namespace NadekoBot.Modules.NSFW +namespace WizBot.Modules.NSFW { internal class NSFWModule : DiscordModule { private readonly Random rng = new Random(); - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.NSFW; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.NSFW; public override void Install(ModuleManager manager) { @@ -27,10 +27,16 @@ public override void Install(ModuleManager manager) .Do(async e => { var tag = e.GetArg("tag")?.Trim() ?? ""; - await e.Channel.SendMessage(":heart: Gelbooru: " + await SearchHelper.GetGelbooruImageLink("rating%3Aexplicit+" + tag).ConfigureAwait(false)) - .ConfigureAwait(false); - await e.Channel.SendMessage(":heart: Danbooru: " + await SearchHelper.GetDanbooruImageLink("rating%3Aexplicit+" + tag).ConfigureAwait(false)) - .ConfigureAwait(false); + var gel = await SearchHelper.GetGelbooruImageLink("rating%3Aexplicit+" + tag).ConfigureAwait(false); + if (gel != null) + await e.Channel.SendMessage(":heart: Gelbooru: " + gel) + .ConfigureAwait(false); + var dan = await SearchHelper.GetDanbooruImageLink("rating%3Aexplicit+" + tag).ConfigureAwait(false); + if (dan != null) + await e.Channel.SendMessage(":heart: Danbooru: " + dan) + .ConfigureAwait(false); + if (dan == null && gel == null) + await e.Channel.SendMessage("`No results.`"); }); 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 +)\n**Usage**: ~danbooru yuri+kissing") @@ -38,7 +44,11 @@ await e.Channel.SendMessage(":heart: Danbooru: " + await SearchHelper.GetDanboor .Do(async e => { var tag = e.GetArg("tag")?.Trim() ?? ""; - await e.Channel.SendMessage(await SearchHelper.GetDanbooruImageLink(tag).ConfigureAwait(false)).ConfigureAwait(false); + 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 +)\n**Usage**: ~gelbooru yuri+kissing") @@ -46,26 +56,27 @@ await e.Channel.SendMessage(":heart: Danbooru: " + await SearchHelper.GetDanboor .Do(async e => { var tag = e.GetArg("tag")?.Trim() ?? ""; - await e.Channel.SendMessage(await SearchHelper.GetGelbooruImageLink(tag).ConfigureAwait(false)).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 +)\n**Usage**: ~safebooru yuri+kissing") - .Parameter("tag", ParameterType.Unparsed) - .Do(async e => - { - var tag = e.GetArg("tag")?.Trim() ?? ""; - await e.Channel.SendMessage(await SearchHelper.GetSafebooruImageLink(tag).ConfigureAwait(false)).ConfigureAwait(false); + 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 +)\n**Usage**: ~gelbooru yuri+kissing") + .Description("Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +)\n**Usage**: ~rule34 yuri+kissing") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { var tag = e.GetArg("tag")?.Trim() ?? ""; - await e.Channel.SendMessage(await SearchHelper.GetRule34ImageLink(tag).ConfigureAwait(false)).ConfigureAwait(false); + 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.\n**Usage**: ~e621 yuri+kissing") + .Description("Shows a random hentai image from e621.net with a given tag. Tag is optional but preffered. Use spaces for multiple tags.\n**Usage**: ~e621 yuri kissing") .Parameter("tag", ParameterType.Unparsed) .Do(async e => { @@ -111,4 +122,4 @@ await e.Channel.SendMessage(":heart: Danbooru: " + await SearchHelper.GetDanboor }); } } -} +} \ No newline at end of file diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs b/WizBot/Modules/Permissions/Classes/PermissionChecker.cs similarity index 90% rename from NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs rename to WizBot/Modules/Permissions/Classes/PermissionChecker.cs index ae54c7c4b..6d94b26b3 100644 --- a/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs +++ b/WizBot/Modules/Permissions/Classes/PermissionChecker.cs @@ -1,12 +1,12 @@ using Discord; using Discord.Commands; using Discord.Commands.Permissions; -using NadekoBot.Classes.JSONModels; +using WizBot.Classes.JSONModels; using System; using System.Collections.Concurrent; using System.Threading.Tasks; -namespace NadekoBot.Modules.Permissions.Classes +namespace WizBot.Modules.Permissions.Classes { internal class PermissionChecker : IPermissionChecker @@ -33,7 +33,7 @@ public bool CanRun(Command command, User user, Channel channel, out string error { error = String.Empty; - if (!NadekoBot.Ready) + if (!WizBot.Ready) return false; if (channel.IsPrivate || channel.Server == null) @@ -51,6 +51,14 @@ public bool CanRun(Command command, User user, Channel channel, out string error timeBlackList.TryAdd(user, DateTime.Now); + if (!channel.IsPrivate && !channel.Server.CurrentUser.GetPermissions(channel).SendMessages) + { + return false; + } + //{ + // user.SendMessage($"I ignored your command in {channel.Server.Name}/#{channel.Name} because i don't have permissions to write to it. Please use `;acm channel_name 0` in that server instead of muting me.").GetAwaiter().GetResult(); + //} + try { //is it a permission command? @@ -69,7 +77,7 @@ public bool CanRun(Command command, User user, Channel channel, out string error return true; ServerPermissions perms; PermissionsHandler.PermissionsDict.TryGetValue(user.Server.Id, out perms); - throw new Exception($"You don't have the necessary role (**{(perms?.PermissionsControllerRole ?? "Nadeko")}**) to change permissions."); + throw new Exception($"You don't have the necessary role (**{(perms?.PermissionsControllerRole ?? "Wiz Bot")}**) to change permissions."); } var permissionType = PermissionsHandler.GetPermissionBanType(command, user, channel); diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs b/WizBot/Modules/Permissions/Classes/PermissionHelper.cs similarity index 93% rename from NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs rename to WizBot/Modules/Permissions/Classes/PermissionHelper.cs index aef62ac84..885ee65f7 100644 --- a/NadekoBot/Modules/Permissions/Classes/PermissionHelper.cs +++ b/WizBot/Modules/Permissions/Classes/PermissionHelper.cs @@ -4,7 +4,7 @@ using System; using System.Linq; -namespace NadekoBot.Modules.Permissions.Classes +namespace WizBot.Modules.Permissions.Classes { internal static class PermissionHelper { @@ -42,7 +42,7 @@ internal static string ValidateModule(string mod) if (string.IsNullOrWhiteSpace(mod)) throw new ArgumentNullException(nameof(mod)); - foreach (var m in NadekoBot.Client.GetService().Modules) + foreach (var m in WizBot.Client.GetService().Modules) { if (m.Name.ToLower().Equals(mod.Trim().ToLower())) return m.Name; @@ -55,7 +55,7 @@ internal static string ValidateCommand(string commandText) if (string.IsNullOrWhiteSpace(commandText)) throw new ArgumentNullException(nameof(commandText)); - foreach (var com in NadekoBot.Client.GetService().AllCommands) + foreach (var com in WizBot.Client.GetService().AllCommands) { if (com.Text.ToLower().Equals(commandText.Trim().ToLower())) return com.Text; diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs b/WizBot/Modules/Permissions/Classes/PermissionsHandler.cs similarity index 87% rename from NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs rename to WizBot/Modules/Permissions/Classes/PermissionsHandler.cs index a9bac105a..61bece147 100644 --- a/NadekoBot/Modules/Permissions/Classes/PermissionsHandler.cs +++ b/WizBot/Modules/Permissions/Classes/PermissionsHandler.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Threading.Tasks; -namespace NadekoBot.Modules.Permissions.Classes +namespace WizBot.Modules.Permissions.Classes { public static class PermissionsHandler { @@ -192,6 +192,60 @@ internal static void SetVerbosity(Server server, bool val) Task.Run(() => WriteServerToJson(serverPerms)); } + internal static void 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); + + Task.Run(() => WriteServerToJson(serverPerms)); + } + + internal static void 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); + + Task.Run(() => WriteServerToJson(serverPerms)); + } + + internal static void 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); + + Task.Run(() => WriteServerToJson(serverPerms)); + } + public static void SetServerModulePermission(Server server, string moduleName, bool value) { var serverPerms = PermissionsDict.GetOrAdd(server.Id, @@ -424,6 +478,18 @@ public Permissions(string name) 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 = ""; @@ -475,7 +541,7 @@ public class ServerPermissions public ServerPermissions(ulong id, string name) { Id = id; - PermissionsControllerRole = "Nadeko"; + PermissionsControllerRole = "WizBot"; Verbose = true; Permissions = new Permissions(name); diff --git a/NadekoBot/Modules/Permissions/Classes/SimpleCheckers.cs b/WizBot/Modules/Permissions/Classes/SimpleCheckers.cs similarity index 91% rename from NadekoBot/Modules/Permissions/Classes/SimpleCheckers.cs rename to WizBot/Modules/Permissions/Classes/SimpleCheckers.cs index 3ab0650da..472532887 100644 --- a/NadekoBot/Modules/Permissions/Classes/SimpleCheckers.cs +++ b/WizBot/Modules/Permissions/Classes/SimpleCheckers.cs @@ -3,14 +3,14 @@ using Discord.Commands.Permissions; using System; -namespace NadekoBot.Modules.Permissions.Classes +namespace WizBot.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); + (com, user, ch) => WizBot.IsOwner(user.Id); public static Func ManageMessages() => (com, user, ch) => user.ServerPermissions.ManageMessages; diff --git a/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs b/WizBot/Modules/Permissions/Commands/FilterInvitesCommand.cs similarity index 94% rename from NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs rename to WizBot/Modules/Permissions/Commands/FilterInvitesCommand.cs index 98e1a2f16..2dc516606 100644 --- a/NadekoBot/Modules/Permissions/Commands/FilterInvitesCommand.cs +++ b/WizBot/Modules/Permissions/Commands/FilterInvitesCommand.cs @@ -1,11 +1,11 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; using System; using System.Text.RegularExpressions; -namespace NadekoBot.Modules.Permissions.Commands +namespace WizBot.Modules.Permissions.Commands { internal class FilterInvitesCommand : DiscordCommand { @@ -14,9 +14,9 @@ internal class FilterInvitesCommand : DiscordCommand public FilterInvitesCommand(DiscordModule module) : base(module) { - NadekoBot.Client.MessageReceived += async (sender, args) => + WizBot.Client.MessageReceived += async (sender, args) => { - if (args.Channel.IsPrivate || args.User.Id == NadekoBot.Client.CurrentUser.Id) return; + if (args.Channel.IsPrivate || args.User.Id == WizBot.Client.CurrentUser.Id) return; try { Classes.ServerPermissions serverPerms; diff --git a/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs b/WizBot/Modules/Permissions/Commands/FilterWordsCommand.cs similarity index 94% rename from NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs rename to WizBot/Modules/Permissions/Commands/FilterWordsCommand.cs index 54819edab..4e6f7268e 100644 --- a/NadekoBot/Modules/Permissions/Commands/FilterWordsCommand.cs +++ b/WizBot/Modules/Permissions/Commands/FilterWordsCommand.cs @@ -1,19 +1,19 @@ using Discord; using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Modules.Permissions.Classes; using System; using System.Linq; -namespace NadekoBot.Modules.Permissions.Commands +namespace WizBot.Modules.Permissions.Commands { internal class FilterWords : DiscordCommand { public FilterWords(DiscordModule module) : base(module) { - NadekoBot.Client.MessageReceived += async (sender, args) => + WizBot.Client.MessageReceived += async (sender, args) => { - if (args.Channel.IsPrivate || args.User.Id == NadekoBot.Client.CurrentUser.Id) return; + if (args.Channel.IsPrivate || args.User.Id == WizBot.Client.CurrentUser.Id) return; try { Classes.ServerPermissions serverPerms; @@ -53,7 +53,7 @@ internal override void Init(CommandGroupBuilder cgb) .Alias(Module.Prefix + "channelfilterwords") .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." + - "\n**Usage**: ;cfi enable #general-chat") + "\n**Usage**: ;cfw enable #general-chat") .Parameter("bool") .Parameter("channel", ParameterType.Optional) .Do(async e => @@ -89,7 +89,7 @@ internal override void Init(CommandGroupBuilder cgb) cgb.CreateCommand(Module.Prefix + "afw") .Alias(Module.Prefix + "addfilteredword") .Description("Adds a new word to the list of filtered words" + - "\n**Usage**: ;aw poop") + "\n**Usage**: ;afw poop") .Parameter("word", ParameterType.Unparsed) .Do(async e => { @@ -152,7 +152,7 @@ await e.Channel.SendMessage($"There are `{serverPerms.Words.Count}` filtered wor cgb.CreateCommand(Module.Prefix + "sfw") .Alias(Module.Prefix + "serverfilterwords") - .Description("Enables or disables automatic deleting of messages containing forbidden words on the server.\n**Usage**: ;sfi disable") + .Description("Enables or disables automatic deleting of messages containing forbidden words on the server.\n**Usage**: ;sfw disable") .Parameter("bool") .Do(async e => { diff --git a/NadekoBot/Modules/Permissions/PermissionsModule.cs b/WizBot/Modules/Permissions/PermissionsModule.cs similarity index 82% rename from NadekoBot/Modules/Permissions/PermissionsModule.cs rename to WizBot/Modules/Permissions/PermissionsModule.cs index 61a5fd521..eddfd22db 100644 --- a/NadekoBot/Modules/Permissions/PermissionsModule.cs +++ b/WizBot/Modules/Permissions/PermissionsModule.cs @@ -1,19 +1,19 @@ using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Extensions; -using NadekoBot.Modules.Games.Commands; -using NadekoBot.Modules.Permissions.Classes; -using NadekoBot.Modules.Permissions.Commands; +using WizBot.Classes.JSONModels; +using WizBot.Extensions; +using WizBot.Modules.Games.Commands; +using WizBot.Modules.Permissions.Classes; +using WizBot.Modules.Permissions.Commands; using System; using System.Linq; using System.Threading.Tasks; -namespace NadekoBot.Modules.Permissions +namespace WizBot.Modules.Permissions { internal class PermissionModule : DiscordModule { - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Permissions; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Permissions; public PermissionModule() { @@ -32,7 +32,7 @@ public override void Install(ModuleManager manager) 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'.") + .Description("Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Wiz Bot'.") .Parameter("role", ParameterType.Unparsed) .Do(async e => { @@ -58,7 +58,94 @@ public override void Install(ModuleManager manager) await e.Channel.SendMessage($"Role `{role.Name}` is now required in order to change permissions.").ConfigureAwait(false); }); - cgb.CreateCommand(Prefix + "verbose") + cgb.CreateCommand(Prefix + "rpc") + .Alias(Prefix + "rolepermissionscopy") + .Description($"Copies BOT PERMISSIONS (not discord permissions) from one role to another.\n**Usage**:`{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."); + return; + } + try + { + var fromRole = PermissionHelper.ValidateRole(e.Server, args[0]); + var toRole = PermissionHelper.ValidateRole(e.Server, args[1]); + + PermissionsHandler.CopyRolePermissions(fromRole, toRole); + await e.Channel.SendMessage($"Copied permission settings from **{fromRole.Name}** to **{toRole.Name}**."); + } + catch (Exception ex) + { + await e.Channel.SendMessage($"💢{ex.Message}"); + } + }); + + cgb.CreateCommand(Prefix + "cpc") + .Alias(Prefix + "channelpermissionscopy") + .Description($"Copies BOT PERMISSIONS (not discord permissions) from one channel to another.\n**Usage**:`{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]); + + PermissionsHandler.CopyChannelPermissions(fromChannel, toChannel); + await e.Channel.SendMessage($"Copied permission settings from **{fromChannel.Name}** to **{toChannel.Name}**."); + } + catch (Exception ex) + { + await e.Channel.SendMessage($"💢{ex.Message}"); + } + }); + + cgb.CreateCommand(Prefix + "upc") + .Alias(Prefix + "userpermissionscopy") + .Description($"Copies BOT PERMISSIONS (not discord permissions) from one role to another.\n**Usage**:`{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."); + return; + } + try + { + var fromUser = PermissionHelper.ValidateUser(e.Server, args[0]); + var toUser = PermissionHelper.ValidateUser(e.Server, args[1]); + + PermissionsHandler.CopyUserPermissions(fromUser, toUser); + await e.Channel.SendMessage($"Copied permission settings from **{fromUser.ToString()}**to * *{toUser.ToString()}**."); + } + 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.\n**Usage**: ;verbose true") .Parameter("arg", ParameterType.Required) @@ -134,7 +221,7 @@ public override void Install(ModuleManager manager) cgb.CreateCommand(Prefix + "userperms") .Alias(Prefix + "up") - .Description("Shows banned permissions for a certain user. No argument means for yourself.\n**Usage**: ;up Kwoth") + .Description("Shows banned permissions for a certain user. No argument means for yourself.\n**Usage**: ;up Wizkiller96") .Parameter("user", ParameterType.Unparsed) .Do(async e => { @@ -292,7 +379,8 @@ public override void Install(ModuleManager manager) var module = PermissionHelper.ValidateModule(e.GetArg("module")); var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - if (e.GetArg("channel")?.ToLower() == "all") + var channelArg = e.GetArg("channel"); + if (channelArg?.ToLower() == "all") { foreach (var channel in e.Server.TextChannels) { @@ -300,9 +388,14 @@ public override void Install(ModuleManager manager) } await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** on **ALL** channels.").ConfigureAwait(false); } - else + else if (string.IsNullOrWhiteSpace(channelArg)) { - var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel")); + PermissionsHandler.SetChannelModulePermission(e.Channel, module, state); + 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); PermissionsHandler.SetChannelModulePermission(channel, module, state); await e.Channel.SendMessage($"Module **{module}** has been **{(state ? "enabled" : "disabled")}** for **{channel.Name}** channel.").ConfigureAwait(false); @@ -417,7 +510,7 @@ public override void Install(ModuleManager manager) { var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - foreach (var module in NadekoBot.Client.GetService().Modules) + foreach (var module in WizBot.Client.GetService().Modules) { PermissionsHandler.SetServerModulePermission(e.Server, module.Name, state); } @@ -444,7 +537,7 @@ public override void Install(ModuleManager manager) 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)) + foreach (var command in WizBot.Client.GetService().AllCommands.Where(c => c.Category == module)) { PermissionsHandler.SetServerCommandPermission(e.Server, command.Text, state); } @@ -469,8 +562,9 @@ public override void Install(ModuleManager manager) try { var state = PermissionHelper.ValidateBool(e.GetArg("bool")); - var channel = PermissionHelper.ValidateChannel(e.Server, e.GetArg("channel")); - foreach (var module in NadekoBot.Client.GetService().Modules) + var chArg = e.GetArg("channel"); + var channel = string.IsNullOrWhiteSpace(chArg) ? e.Channel :PermissionHelper.ValidateChannel(e.Server, chArg); + foreach (var module in WizBot.Client.GetService().Modules) { PermissionsHandler.SetChannelModulePermission(channel, module.Name, state); } @@ -499,7 +593,7 @@ public override void Install(ModuleManager manager) 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)) + foreach (var command in WizBot.Client.GetService().AllCommands.Where(c => c.Category == module)) { PermissionsHandler.SetChannelCommandPermission(channel, command.Text, state); } @@ -525,7 +619,7 @@ public override void Install(ModuleManager manager) { var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("role")); - foreach (var module in NadekoBot.Client.GetService().Modules) + foreach (var module in WizBot.Client.GetService().Modules) { PermissionsHandler.SetRoleModulePermission(role, module.Name, state); } @@ -554,7 +648,7 @@ public override void Install(ModuleManager manager) var state = PermissionHelper.ValidateBool(e.GetArg("bool")); var module = PermissionHelper.ValidateModule(e.GetArg("module")); var role = PermissionHelper.ValidateRole(e.Server, e.GetArg("channel")); - foreach (var command in NadekoBot.Client.GetService().AllCommands.Where(c => c.Category == module)) + foreach (var command in WizBot.Client.GetService().AllCommands.Where(c => c.Category == module)) { PermissionsHandler.SetRoleCommandPermission(role, command.Text, state); } @@ -580,7 +674,7 @@ await Task.Run(async () => { if (!e.Message.MentionedUsers.Any()) return; var usr = e.Message.MentionedUsers.First(); - NadekoBot.Config.UserBlacklist.Add(usr.Id); + WizBot.Config.UserBlacklist.Add(usr.Id); ConfigHandler.SaveConfig(); await e.Channel.SendMessage($"`Sucessfully blacklisted user {usr.Name}`").ConfigureAwait(false); }).ConfigureAwait(false); @@ -596,9 +690,9 @@ await Task.Run(async () => { if (!e.Message.MentionedUsers.Any()) return; var usr = e.Message.MentionedUsers.First(); - if (NadekoBot.Config.UserBlacklist.Contains(usr.Id)) + if (WizBot.Config.UserBlacklist.Contains(usr.Id)) { - NadekoBot.Config.UserBlacklist.Remove(usr.Id); + WizBot.Config.UserBlacklist.Remove(usr.Id); ConfigHandler.SaveConfig(); await e.Channel.SendMessage($"`Sucessfully unblacklisted user {usr.Name}`").ConfigureAwait(false); } @@ -610,7 +704,7 @@ await Task.Run(async () => }); cgb.CreateCommand(Prefix + "cbl") - .Description("Blacklists a mentioned channel (#general for example).\n**Usage**: ;ubl [channel_mention]") + .Description("Blacklists a mentioned channel (#general for example).\n**Usage**: ;cbl [channel_mention]") .Parameter("channel", ParameterType.Unparsed) .Do(async e => { @@ -618,7 +712,7 @@ await Task.Run(async () => { if (!e.Message.MentionedChannels.Any()) return; var ch = e.Message.MentionedChannels.First(); - NadekoBot.Config.UserBlacklist.Add(ch.Id); + WizBot.Config.UserBlacklist.Add(ch.Id); ConfigHandler.SaveConfig(); await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false); }).ConfigureAwait(false); @@ -633,14 +727,14 @@ await Task.Run(async () => { if (!e.Message.MentionedChannels.Any()) return; var ch = e.Message.MentionedChannels.First(); - NadekoBot.Config.UserBlacklist.Remove(ch.Id); + WizBot.Config.UserBlacklist.Remove(ch.Id); ConfigHandler.SaveConfig(); await e.Channel.SendMessage($"`Sucessfully blacklisted channel {ch.Name}`").ConfigureAwait(false); }).ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "sbl") - .Description("Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY**\n**Usage**: ;usl [servername/serverid]") + .Description("Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY**\n**Usage**: ;sbl [servername/serverid]") .Parameter("server", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -650,15 +744,15 @@ 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(); + var server = WizBot.Client.Servers.FirstOrDefault(s => s.Id.ToString() == arg) ?? + WizBot.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); + WizBot.Config.ServerBlacklist.Add(serverId); ConfigHandler.SaveConfig(); //cleanup trivias and typeracing Modules.Games.Commands.Trivia.TriviaGame trivia; diff --git a/NadekoBot/Modules/Pokemon/PokeStats.cs b/WizBot/Modules/Pokemon/PokeStats.cs similarity index 91% rename from NadekoBot/Modules/Pokemon/PokeStats.cs rename to WizBot/Modules/Pokemon/PokeStats.cs index b97950437..8a2dfc0ae 100644 --- a/NadekoBot/Modules/Pokemon/PokeStats.cs +++ b/WizBot/Modules/Pokemon/PokeStats.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace NadekoBot.Modules.Pokemon +namespace WizBot.Modules.Pokemon { class PokeStats { diff --git a/NadekoBot/Modules/Pokemon/PokemonModule.cs b/WizBot/Modules/Pokemon/PokemonModule.cs similarity index 89% rename from NadekoBot/Modules/Pokemon/PokemonModule.cs rename to WizBot/Modules/Pokemon/PokemonModule.cs index aa271d3b0..a1ce86901 100644 --- a/NadekoBot/Modules/Pokemon/PokemonModule.cs +++ b/WizBot/Modules/Pokemon/PokemonModule.cs @@ -1,20 +1,20 @@ 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 WizBot.Classes; +using WizBot.Classes.JSONModels; +using WizBot.DataModels; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -namespace NadekoBot.Modules.Pokemon +namespace WizBot.Modules.Pokemon { class PokemonModule : DiscordModule { - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Pokemon; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Pokemon; private ConcurrentDictionary Stats = new ConcurrentDictionary(); @@ -48,11 +48,11 @@ private PokemonType GetPokeType(ulong id) { return stringToPokemonType(setTypes[(long)id]); } - int count = NadekoBot.Config.PokemonTypes.Count; + int count = WizBot.Config.PokemonTypes.Count; int remainder = Math.Abs((int)(id % (ulong)count)); - return NadekoBot.Config.PokemonTypes[remainder]; + return WizBot.Config.PokemonTypes[remainder]; } @@ -60,7 +60,7 @@ private PokemonType GetPokeType(ulong id) private PokemonType stringToPokemonType(string v) { var str = v.ToUpperInvariant(); - var list = NadekoBot.Config.PokemonTypes; + var list = WizBot.Config.PokemonTypes; foreach (PokemonType p in list) { if (str == p.Name) @@ -210,7 +210,7 @@ public override void Install(ModuleManager manager) }); cgb.CreateCommand(Prefix + "heal") - .Description($"Heals someone. Revives those that fainted. Costs a {NadekoBot.Config.CurrencyName} \n**Usage**:{Prefix}revive @someone") + .Description($"Heals someone. Revives those that fainted. Costs a {WizBot.Config.CurrencyName} \n**Usage**:{Prefix}revive @someone") .Parameter("target", ParameterType.Unparsed) .Do(async e => { @@ -238,7 +238,7 @@ public override void Install(ModuleManager manager) 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); + await e.Channel.SendMessage($"{e.User.Mention} you don't have enough {WizBot.Config.CurrencyName}s! \nYou still need {amount - pts} {WizBot.Config.CurrencySign} to be able to do this!").ConfigureAwait(false); return; } var target = (usr.Id == e.User.Id) ? "yourself" : usr.Name; @@ -249,11 +249,11 @@ public override void Install(ModuleManager manager) { //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); + await e.Channel.SendMessage($"{e.User.Name} revived {usr.Name} with one {WizBot.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); + var vowelFirst = new[] { 'a', 'e', 'i', 'o', 'u' }.Contains(WizBot.Config.CurrencyName[0]); + await e.Channel.SendMessage($"{e.User.Name} healed {usr.Name} for {targetStats.MaxHp - HP} HP with {(vowelFirst ? "an" : "a")} {WizBot.Config.CurrencySign}").ConfigureAwait(false); return; } else @@ -282,7 +282,7 @@ public override void Install(ModuleManager manager) }); cgb.CreateCommand(Prefix + "settype") - .Description($"Set your poketype. Costs a {NadekoBot.Config.CurrencyName}.\n**Usage**: {Prefix}settype fire") + .Description($"Set your poketype. Costs a {WizBot.Config.CurrencyName}.\n**Usage**: {Prefix}settype fire") .Parameter("targetType", ParameterType.Unparsed) .Do(async e => { @@ -292,7 +292,7 @@ public override void Install(ModuleManager manager) var targetType = stringToPokemonType(targetTypeStr); if (targetType == null) { - await e.Channel.SendMessage("Invalid type specified. Type must be one of:\nNORMAL, FIRE, WATER, ELECTRIC, GRASS, ICE, FIGHTING, POISON, GROUND, FLYING, PSYCHIC, BUG, ROCK, GHOST, DRAGON, DARK, STEEL").ConfigureAwait(false); + await e.Channel.SendMessage("Invalid type specified. Type must be one of:\n" + string.Join(", ", WizBot.Config.PokemonTypes.Select(t => t.Name.ToUpperInvariant()))).ConfigureAwait(false); return; } if (targetType == GetPokeType(e.User.Id)) @@ -306,7 +306,7 @@ public override void Install(ModuleManager manager) 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); + await e.Channel.SendMessage($"{e.User.Mention} you don't have enough {WizBot.Config.CurrencyName}s! \nYou still need {amount - pts} {WizBot.Config.CurrencySign} to be able to do this!").ConfigureAwait(false); return; } FlowersHandler.RemoveFlowers(e.User, $"set usertype to {targetTypeStr}", amount); @@ -327,7 +327,7 @@ public override void Install(ModuleManager manager) //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); + await e.Channel.SendMessage($"Set type of {e.User.Mention} to {targetTypeStr}{targetType.Icon} for a {WizBot.Config.CurrencySign}").ConfigureAwait(false); }); }); } diff --git a/WizBot/Modules/Programming/Commands/HaskellRepl.cs b/WizBot/Modules/Programming/Commands/HaskellRepl.cs new file mode 100644 index 000000000..a4cbf000c --- /dev/null +++ b/WizBot/Modules/Programming/Commands/HaskellRepl.cs @@ -0,0 +1,92 @@ +using Discord; +using Discord.Commands; +using WizBot.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 WizBot.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)); + }); + } + } +} \ No newline at end of file diff --git a/WizBot/Modules/Programming/ProgrammingModule.cs b/WizBot/Modules/Programming/ProgrammingModule.cs new file mode 100644 index 000000000..402286b2e --- /dev/null +++ b/WizBot/Modules/Programming/ProgrammingModule.cs @@ -0,0 +1,26 @@ +using Discord.Modules; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; +using WizBot.Modules.Programming.Commands; + +namespace WizBot.Modules.Programming +{ + class ProgrammingModule : DiscordModule + { + public override string Prefix => WizBot.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)); + }); + } + } +} \ No newline at end of file diff --git a/NadekoBot/Modules/Searches/Commands/ConverterCommand.cs b/WizBot/Modules/Searches/Commands/ConverterCommand.cs similarity index 90% rename from NadekoBot/Modules/Searches/Commands/ConverterCommand.cs rename to WizBot/Modules/Searches/Commands/ConverterCommand.cs index 8d820df44..1cf8c4f60 100644 --- a/NadekoBot/Modules/Searches/Commands/ConverterCommand.cs +++ b/WizBot/Modules/Searches/Commands/ConverterCommand.cs @@ -1,5 +1,5 @@ using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; using ScaredFingers.UnitsConversion; using System; using System.Collections.Generic; @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace NadekoBot.Modules.Searches.Commands +namespace WizBot.Modules.Searches.Commands { class ConverterCommand : DiscordCommand { @@ -92,6 +92,8 @@ private Func ConvertFunc() => } 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())); @@ -109,10 +111,17 @@ private void reInitCurrencyConverterTable() { if (lastChanged == null || lastChanged.DayOfYear != DateTime.Now.DayOfYear) { - exchangeRateProvider = new WebExchangeRatesProvider(); - currTable = new CurrencyExchangeTable(exchangeRateProvider); - lastChanged = DateTime.Now; - } + 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) diff --git a/NadekoBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs b/WizBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs similarity index 96% rename from NadekoBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs rename to WizBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs index 87397dbf9..a0e220116 100644 --- a/NadekoBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs +++ b/WizBot/Modules/Searches/Commands/IMDB/ImdbMovie.cs @@ -1,8 +1,8 @@ -using NadekoBot.Extensions; +using WizBot.Extensions; using System.Collections.Generic; using System.Web; -namespace NadekoBot.Modules.Searches.Commands.IMDB +namespace WizBot.Modules.Searches.Commands.IMDB { public class ImdbMovie { diff --git a/NadekoBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs b/WizBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs similarity index 99% rename from NadekoBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs rename to WizBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs index 1735eda63..7e8c9be0c 100644 --- a/NadekoBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs +++ b/WizBot/Modules/Searches/Commands/IMDB/ImdbScraper.cs @@ -18,7 +18,7 @@ * Last Updated: Feb, 2016 *******************************************************************************/ -namespace NadekoBot.Modules.Searches.Commands.IMDB +namespace WizBot.Modules.Searches.Commands.IMDB { public static class ImdbScraper { diff --git a/NadekoBot/Modules/Searches/Commands/LoLCommands.cs b/WizBot/Modules/Searches/Commands/LoLCommands.cs similarity index 95% rename from NadekoBot/Modules/Searches/Commands/LoLCommands.cs rename to WizBot/Modules/Searches/Commands/LoLCommands.cs index 075a5b2e5..22d9060cd 100644 --- a/NadekoBot/Modules/Searches/Commands/LoLCommands.cs +++ b/WizBot/Modules/Searches/Commands/LoLCommands.cs @@ -1,6 +1,6 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Extensions; +using WizBot.Classes; +using WizBot.Extensions; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -9,7 +9,7 @@ using System.Text; using System.Threading.Tasks; -namespace NadekoBot.Modules.Searches.Commands +namespace WizBot.Modules.Searches.Commands { internal class LoLCommands : DiscordCommand { @@ -21,6 +21,14 @@ private class CachedChampion 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 readonly object cacheLock = new object(); @@ -89,7 +97,7 @@ internal override void Init(CommandGroupBuilder cgb) 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)); + var allData = JArray.Parse(await Classes.SearchHelper.GetResponseStringAsync($"http://api.champion.gg/champion/{name}?api_key={WizBot.Creds.LOLAPIKey}").ConfigureAwait(false)); JToken data = null; if (role != null) { @@ -134,7 +142,7 @@ internal override void Init(CommandGroupBuilder cgb) roles[i] = ">" + roles[i] + "<"; } var general = JArray.Parse(await SearchHelper.GetResponseStringAsync($"http://api.champion.gg/stats/" + - $"champs/{name}?api_key={NadekoBot.Creds.LOLAPIKey}") + $"champs/{name}?api_key={WizBot.Creds.LOLAPIKey}") .ConfigureAwait(false)) .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"]; if (general == null) @@ -224,7 +232,6 @@ internal override void Init(CommandGroupBuilder cgb) //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"]}% @@ -285,7 +292,7 @@ internal override void Init(CommandGroupBuilder cgb) .Do(async e => { - var showCount = 6; + var showCount = 8; //http://api.champion.gg/stats/champs/mostBanned?api_key=YOUR_API_TOKEN&page=1&limit=2 try { @@ -293,19 +300,19 @@ internal override void Init(CommandGroupBuilder cgb) await Classes .SearchHelper .GetResponseStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" + - $"api_key={NadekoBot.Creds.LOLAPIKey}&page=1&" + + $"api_key={WizBot.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 < data.Count; i++) + for (var i = 0; i < dataList.Count; i++) { if (i % 2 == 0 && i != 0) sb.AppendLine(); - sb.Append($"`{i + 1}.` **{data[i]["name"]}** "); - //sb.AppendLine($" ({data[i]["general"]["banRate"]}%)"); + sb.Append($"`{i + 1}.` **{dataList[i]["name"]}** "); + //sb.AppendLine($" ({dataList[i]["general"]["banRate"]}%)"); } await e.Channel.SendMessage(sb.ToString()).ConfigureAwait(false); @@ -381,4 +388,4 @@ private static string ResolvePos(string pos) } } } -} +} \ No newline at end of file diff --git a/NadekoBot/Modules/Searches/Commands/RedditCommand.cs b/WizBot/Modules/Searches/Commands/RedditCommand.cs similarity index 81% rename from NadekoBot/Modules/Searches/Commands/RedditCommand.cs rename to WizBot/Modules/Searches/Commands/RedditCommand.cs index 7a17d3472..2764a6a26 100644 --- a/NadekoBot/Modules/Searches/Commands/RedditCommand.cs +++ b/WizBot/Modules/Searches/Commands/RedditCommand.cs @@ -1,7 +1,7 @@ using Discord.Commands; -using NadekoBot.Classes; +using WizBot.Classes; -namespace NadekoBot.Modules.Searches.Commands +namespace WizBot.Modules.Searches.Commands { class RedditCommand : DiscordCommand { diff --git a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs b/WizBot/Modules/Searches/Commands/StreamNotifications.cs similarity index 98% rename from NadekoBot/Modules/Searches/Commands/StreamNotifications.cs rename to WizBot/Modules/Searches/Commands/StreamNotifications.cs index ffa38e5c2..563e63399 100644 --- a/NadekoBot/Modules/Searches/Commands/StreamNotifications.cs +++ b/WizBot/Modules/Searches/Commands/StreamNotifications.cs @@ -1,7 +1,7 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Classes; +using WizBot.Classes.JSONModels; +using WizBot.Modules.Permissions.Classes; using Newtonsoft.Json.Linq; using System; using System.Collections.Concurrent; @@ -9,7 +9,7 @@ using System.Threading.Tasks; using System.Timers; -namespace NadekoBot.Modules.Searches.Commands +namespace WizBot.Modules.Searches.Commands { internal class StreamNotifications : DiscordCommand { @@ -47,7 +47,7 @@ public StreamNotifications(DiscordModule module) : base(module) if (data.Item1 != stream.LastStatus) { stream.LastStatus = data.Item1; - var server = NadekoBot.Client.GetServer(stream.ServerId); + var server = WizBot.Client.GetServer(stream.ServerId); var channel = server?.GetChannel(stream.ChannelId); if (channel == null) continue; diff --git a/WizBot/Modules/Searches/Commands/WowJokes.cs b/WizBot/Modules/Searches/Commands/WowJokes.cs new file mode 100644 index 000000000..39025f561 --- /dev/null +++ b/WizBot/Modules/Searches/Commands/WowJokes.cs @@ -0,0 +1,36 @@ +using Discord.Commands; +using WizBot.Classes; +using WizBot.Classes.JSONModels; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace WizBot.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.") + .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()); + }); + } + } +} \ No newline at end of file diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/WizBot/Modules/Searches/SearchesModule.cs similarity index 77% rename from NadekoBot/Modules/Searches/SearchesModule.cs rename to WizBot/Modules/Searches/SearchesModule.cs index 94c3dcb79..fed2d7fc8 100644 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ b/WizBot/Modules/Searches/SearchesModule.cs @@ -1,11 +1,11 @@ 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 WizBot.Classes; +using WizBot.Classes.JSONModels; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; +using WizBot.Modules.Searches.Commands; +using WizBot.Modules.Searches.Commands.IMDB; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -16,7 +16,7 @@ using System.Net; using System.Net.Http; -namespace NadekoBot.Modules.Searches +namespace WizBot.Modules.Searches { internal class SearchesModule : DiscordModule { @@ -27,10 +27,11 @@ public SearchesModule() commands.Add(new StreamNotifications(this)); commands.Add(new ConverterCommand(this)); commands.Add(new RedditCommand(this)); + commands.Add(new WowJokeCommand(this)); rng = new Random(); } - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Searches; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Searches; public override void Install(ModuleManager manager) { @@ -42,7 +43,7 @@ public override void Install(ModuleManager manager) commands.ForEach(cmd => cmd.Init(cgb)); cgb.CreateCommand(Prefix + "we") - .Description("Shows weather data for a specified city and a country BOTH ARE REQUIRED. Weather api is very random if you make a mistake.") + .Description($"Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations.\n**Usage**: {Prefix}we Moscow RF") .Parameter("city", ParameterType.Required) .Parameter("country", ParameterType.Required) .Do(async e => @@ -68,7 +69,13 @@ await e.Channel.SendMessage( { if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; - var shortUrl = await SearchHelper.ShortenUrl(await SearchHelper.FindYoutubeUrlByKeywords(e.GetArg("query")).ConfigureAwait(false)).ConfigureAwait(false); + 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); }); @@ -137,6 +144,7 @@ await e.Channel.SendMessage( }); cgb.CreateCommand(Prefix + "randomcat") + .Alias(Prefix + "meow") .Description("Shows a random cat image.") .Do(async e => { @@ -154,21 +162,21 @@ await SearchHelper.GetResponseStringAsync("http://www.random.cat/meow").Configur 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 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={WizBot.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."); - } - } + { + 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") @@ -180,21 +188,22 @@ await SearchHelper.GetResponseStringAsync("http://www.random.cat/meow").Configur 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, 150) }&fields=items%2Flink&key={NadekoBot.Creds.GoogleAPIKey}"; + var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=50&searchType=image&start={ rng.Next(1, 50) }&fields=items%2Flink&key={WizBot.Creds.GoogleAPIKey}"; var obj = JObject.Parse(await SearchHelper.GetResponseStringAsync(reqString).ConfigureAwait(false)); - await e.Channel.SendMessage(obj["items"][0]["link"].ToString()).ConfigureAwait(false); + var items = obj["items"] as JArray; + await e.Channel.SendMessage(items[rng.Next(0, items.Count)]["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."); - } - } + { + 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.") @@ -218,7 +227,7 @@ await e.Channel.SendMessage(await $"http://lmgtfy.com/?q={ Uri.EscapeUriString(e return; } await e.Channel.SendIsTyping().ConfigureAwait(false); - var headers = new Dictionary { { "X-Mashape-Key", NadekoBot.Creds.MashapeKey } }; + var headers = new Dictionary { { "X-Mashape-Key", WizBot.Creds.MashapeKey } }; var res = await SearchHelper.GetResponseStringAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}", headers) .ConfigureAwait(false); try @@ -291,7 +300,7 @@ await e.Channel.SendFile(arg + ".png", (await images.MergeAsync()).ToStream(Syst return; } await e.Channel.SendIsTyping().ConfigureAwait(false); - var headers = new Dictionary { { "X-Mashape-Key", NadekoBot.Creds.MashapeKey } }; + var headers = new Dictionary { { "X-Mashape-Key", WizBot.Creds.MashapeKey } }; var res = await SearchHelper.GetResponseStringAsync($"https://mashape-community-urban-dictionary.p.mashape.com/define?term={Uri.EscapeUriString(arg)}", headers).ConfigureAwait(false); try { @@ -320,7 +329,7 @@ await e.Channel.SendFile(arg + ".png", (await images.MergeAsync()).ToStream(Syst return; } await e.Channel.SendIsTyping().ConfigureAwait(false); - var headers = new Dictionary { { "X-Mashape-Key", NadekoBot.Creds.MashapeKey } }; + var headers = new Dictionary { { "X-Mashape-Key", WizBot.Creds.MashapeKey } }; var res = await SearchHelper.GetResponseStringAsync($"https://tagdef.p.mashape.com/one.{Uri.EscapeUriString(arg)}.json", headers).ConfigureAwait(false); try { @@ -341,7 +350,7 @@ await e.Channel.SendFile(arg + ".png", (await images.MergeAsync()).ToStream(Syst .Description("Shows a random quote.") .Do(async e => { - var quote = NadekoBot.Config.Quotes[rng.Next(0, NadekoBot.Config.Quotes.Count)].ToString(); + var quote = WizBot.Config.Quotes[rng.Next(0, WizBot.Config.Quotes.Count)].ToString(); await e.Channel.SendMessage(quote).ConfigureAwait(false); }); @@ -409,8 +418,68 @@ await e.Channel.SendFile(arg + ".png", (await images.MergeAsync()).ToStream(Syst 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.") + .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 +)\n**Usage**: ~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") + .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.\n**Usage**: `~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(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()); + }); + }); } } } - diff --git a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs b/WizBot/Modules/Translator/Helpers/GoogleTranslator.cs similarity index 99% rename from NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs rename to WizBot/Modules/Translator/Helpers/GoogleTranslator.cs index eb0e466ce..c84e3d9d1 100644 --- a/NadekoBot/Modules/Translator/Helpers/GoogleTranslator.cs +++ b/WizBot/Modules/Translator/Helpers/GoogleTranslator.cs @@ -9,7 +9,7 @@ using System.Net; using System.Web; -namespace NadekoBot.Modules.Translator.Helpers +namespace WizBot.Modules.Translator.Helpers { /// /// Translates text using Google's online language tools. diff --git a/NadekoBot/Modules/Translator/TranslateCommand.cs b/WizBot/Modules/Translator/TranslateCommand.cs similarity index 80% rename from NadekoBot/Modules/Translator/TranslateCommand.cs rename to WizBot/Modules/Translator/TranslateCommand.cs index 0c4ae5027..95f599d29 100644 --- a/NadekoBot/Modules/Translator/TranslateCommand.cs +++ b/WizBot/Modules/Translator/TranslateCommand.cs @@ -1,10 +1,10 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Translator.Helpers; +using WizBot.Classes; +using WizBot.Modules.Translator.Helpers; using System; using System.Threading.Tasks; -namespace NadekoBot.Modules.Translator +namespace WizBot.Modules.Translator { class TranslateCommand : DiscordCommand { @@ -13,7 +13,8 @@ public TranslateCommand(DiscordModule module) : base(module) { } internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "trans") - .Description("Translates from>to text. From the given language to the destiation language.") + .Alias(Module.Prefix + "translate") + .Description($"Translates from>to text. From the given language to the destiation language.\n**Usage**: {Module.Prefix}trans en>fr Hello") .Parameter("langs", ParameterType.Required) .Parameter("text", ParameterType.Unparsed) .Do(TranslateFunc()); diff --git a/NadekoBot/Modules/Translator/TranslatorModule.cs b/WizBot/Modules/Translator/TranslatorModule.cs similarity index 72% rename from NadekoBot/Modules/Translator/TranslatorModule.cs rename to WizBot/Modules/Translator/TranslatorModule.cs index 1b333a6b7..de272e039 100644 --- a/NadekoBot/Modules/Translator/TranslatorModule.cs +++ b/WizBot/Modules/Translator/TranslatorModule.cs @@ -1,8 +1,8 @@ using Discord.Modules; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; -namespace NadekoBot.Modules.Translator +namespace WizBot.Modules.Translator { internal class TranslatorModule : DiscordModule { @@ -12,7 +12,7 @@ public TranslatorModule() commands.Add(new ValidLanguagesCommand(this)); } - public override string Prefix { get; } = NadekoBot.Config.CommandPrefixes.Searches; + public override string Prefix { get; } = WizBot.Config.CommandPrefixes.Searches; public override void Install(ModuleManager manager) { diff --git a/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs b/WizBot/Modules/Translator/ValidLanguagesCommand.cs similarity index 93% rename from NadekoBot/Modules/Translator/ValidLanguagesCommand.cs rename to WizBot/Modules/Translator/ValidLanguagesCommand.cs index ede25f138..d1a35a312 100644 --- a/NadekoBot/Modules/Translator/ValidLanguagesCommand.cs +++ b/WizBot/Modules/Translator/ValidLanguagesCommand.cs @@ -1,10 +1,10 @@ using Discord.Commands; -using NadekoBot.Classes; -using NadekoBot.Modules.Translator.Helpers; +using WizBot.Classes; +using WizBot.Modules.Translator.Helpers; using System; using System.Threading.Tasks; -namespace NadekoBot.Modules.Translator +namespace WizBot.Modules.Translator { class ValidLanguagesCommand : DiscordCommand { diff --git a/NadekoBot/Modules/Trello/TrelloModule.cs b/WizBot/Modules/Trello/TrelloModule.cs similarity index 91% rename from NadekoBot/Modules/Trello/TrelloModule.cs rename to WizBot/Modules/Trello/TrelloModule.cs index 01d69ad09..3c0fc6139 100644 --- a/NadekoBot/Modules/Trello/TrelloModule.cs +++ b/WizBot/Modules/Trello/TrelloModule.cs @@ -1,20 +1,20 @@ using Discord.Modules; using Manatee.Trello; using Manatee.Trello.ManateeJson; -using NadekoBot.Extensions; -using NadekoBot.Modules.Permissions.Classes; +using WizBot.Extensions; +using WizBot.Modules.Permissions.Classes; using System; using System.Collections.Generic; using System.Linq; using System.Timers; using Action = Manatee.Trello.Action; -namespace NadekoBot.Modules.Trello +namespace WizBot.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 string Prefix { get; } = WizBot.Config.CommandPrefixes.Trello; public override void Install(ModuleManager manager) { @@ -26,7 +26,7 @@ public override void Install(ModuleManager manager) TrelloConfiguration.Deserializer = serializer; TrelloConfiguration.JsonFactory = new ManateeFactory(); TrelloConfiguration.RestClientProvider = new Manatee.Trello.WebApi.WebApiClientProvider(); - TrelloAuthorization.Default.AppKey = NadekoBot.Creds.TrelloAppKey; + TrelloAuthorization.Default.AppKey = WizBot.Creds.TrelloAppKey; //TrelloAuthorization.Default.UserToken = "[your user token]"; Discord.Channel bound = null; @@ -74,7 +74,7 @@ public override void Install(ModuleManager manager) .Parameter("code", Discord.Commands.ParameterType.Required) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id) || NadekoBot.IsBot) return; + if (!WizBot.IsOwner(e.User.Id) || WizBot.IsBot) return; try { await (await client.GetInvite(e.GetArg("code")).ConfigureAwait(false)).Accept() @@ -93,7 +93,7 @@ public override void Install(ModuleManager manager) .Parameter("board_id", Discord.Commands.ParameterType.Required) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; + if (!WizBot.IsOwner(e.User.Id)) return; if (bound != null) return; try { @@ -113,7 +113,7 @@ public override void Install(ModuleManager manager) .Description("Unbinds a bot from the channel and board.") .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; + if (!WizBot.IsOwner(e.User.Id)) return; if (bound == null || bound != e.Channel) return; t.Stop(); bound = null; @@ -127,7 +127,7 @@ public override void Install(ModuleManager manager) .Description("Lists all lists yo ;)") .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; + if (!WizBot.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); @@ -138,7 +138,7 @@ public override void Install(ModuleManager manager) .Parameter("list_name", Discord.Commands.ParameterType.Unparsed) .Do(async e => { - if (!NadekoBot.IsOwner(e.User.Id)) return; + if (!WizBot.IsOwner(e.User.Id)) return; if (bound == null || board == null || bound != e.Channel || e.GetArg("list_name") == null) return; int num; diff --git a/NadekoBot/Properties/AssemblyInfo.cs b/WizBot/Properties/AssemblyInfo.cs similarity index 83% rename from NadekoBot/Properties/AssemblyInfo.cs rename to WizBot/Properties/AssemblyInfo.cs index 2f38b1851..4b1bc3de6 100644 --- a/NadekoBot/Properties/AssemblyInfo.cs +++ b/WizBot/Properties/AssemblyInfo.cs @@ -5,13 +5,13 @@ // 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: AssemblyCompany("Kwoth & Wizkiller96")] +[assembly: AssemblyCopyright("Copyright © WizNet 2016")] +[assembly: AssemblyProduct("WizBot")] [assembly: AssemblyVersion("0.9.*")] -[assembly: AssemblyTitle("NadekoBot")] +[assembly: AssemblyTitle("WizBot")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyTrademark("")] +[assembly: AssemblyTrademark("WizNet")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible diff --git a/NadekoBot/Properties/Resources.Designer.cs b/WizBot/Properties/Resources.Designer.cs similarity index 96% rename from NadekoBot/Properties/Resources.Designer.cs rename to WizBot/Properties/Resources.Designer.cs index b393bbf6e..b56586e81 100644 --- a/NadekoBot/Properties/Resources.Designer.cs +++ b/WizBot/Properties/Resources.Designer.cs @@ -1,743 +1,743 @@ -//------------------------------------------------------------------------------ -// -// 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 hidden { - get { - object obj = ResourceManager.GetObject("hidden", 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 tails { - get { - object obj = ResourceManager.GetObject("tails", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - } -} +//------------------------------------------------------------------------------ +// +// 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 WizBot.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("WizBot.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 hidden { + get { + object obj = ResourceManager.GetObject("hidden", 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 tails { + get { + object obj = ResourceManager.GetObject("tails", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/NadekoBot/Properties/Resources.resx b/WizBot/Properties/Resources.resx similarity index 100% rename from NadekoBot/Properties/Resources.resx rename to WizBot/Properties/Resources.resx diff --git a/WizBot/SQLite.cs b/WizBot/SQLite.cs new file mode 100644 index 000000000..e74447f63 --- /dev/null +++ b/WizBot/SQLite.cs @@ -0,0 +1,3278 @@ +// +// 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/WizBot/SQLiteAsync.cs b/WizBot/SQLiteAsync.cs new file mode 100644 index 000000000..79b91cba7 --- /dev/null +++ b/WizBot/SQLiteAsync.cs @@ -0,0 +1,503 @@ +// +// Copyright (c) 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. +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace SQLite +{ + public partial class SQLiteAsyncConnection + { + SQLiteConnectionString _connectionString; + SQLiteOpenFlags _openFlags; + + public SQLiteAsyncConnection(string databasePath, bool storeDateTimeAsTicks = false) + : this(databasePath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create, storeDateTimeAsTicks) + { + } + + public SQLiteAsyncConnection(string databasePath, SQLiteOpenFlags openFlags, bool storeDateTimeAsTicks = false) + { + _openFlags = openFlags; + _connectionString = new SQLiteConnectionString(databasePath, storeDateTimeAsTicks); + } + + SQLiteConnectionWithLock GetConnection () + { + return SQLiteConnectionPool.Shared.GetConnection (_connectionString, _openFlags); + } + + public Task CreateTableAsync () + where T : new () + { + return CreateTablesAsync (typeof (T)); + } + + public Task CreateTablesAsync () + where T : new () + where T2 : new () + { + return CreateTablesAsync (typeof (T), typeof (T2)); + } + + public Task CreateTablesAsync () + where T : new () + where T2 : new () + where T3 : new () + { + return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3)); + } + + public Task CreateTablesAsync () + where T : new () + where T2 : new () + where T3 : new () + where T4 : new () + { + return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3), typeof (T4)); + } + + public Task CreateTablesAsync () + where T : new () + where T2 : new () + where T3 : new () + where T4 : new () + where T5 : new () + { + return CreateTablesAsync (typeof (T), typeof (T2), typeof (T3), typeof (T4), typeof (T5)); + } + + public Task CreateTablesAsync (params Type[] types) + { + return Task.Factory.StartNew (() => { + CreateTablesResult result = new CreateTablesResult (); + var conn = GetConnection (); + using (conn.Lock ()) { + foreach (Type type in types) { + int aResult = conn.CreateTable (type); + result.Results[type] = aResult; + } + } + return result; + }); + } + + public Task DropTableAsync () + where T : new () + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.DropTable (); + } + }); + } + + public Task InsertAsync (object item) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Insert (item); + } + }); + } + + public Task UpdateAsync (object item) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Update (item); + } + }); + } + + public Task DeleteAsync (object item) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Delete (item); + } + }); + } + + public Task GetAsync(object pk) + where T : new() + { + return Task.Factory.StartNew(() => + { + var conn = GetConnection(); + using (conn.Lock()) + { + return conn.Get(pk); + } + }); + } + + public Task FindAsync (object pk) + where T : new () + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Find (pk); + } + }); + } + + public Task GetAsync (Expression> predicate) + where T : new() + { + return Task.Factory.StartNew(() => + { + var conn = GetConnection(); + using (conn.Lock()) + { + return conn.Get (predicate); + } + }); + } + + public Task FindAsync (Expression> predicate) + where T : new () + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Find (predicate); + } + }); + } + + public Task ExecuteAsync (string query, params object[] args) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Execute (query, args); + } + }); + } + + public Task InsertAllAsync (IEnumerable items) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.InsertAll (items); + } + }); + } + + public Task UpdateAllAsync (IEnumerable items) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.UpdateAll (items); + } + }); + } + + [Obsolete("Will cause a deadlock if any call in action ends up in a different thread. Use RunInTransactionAsync(Action) instead.")] + public Task RunInTransactionAsync (Action action) + { + return Task.Factory.StartNew (() => { + var conn = this.GetConnection (); + using (conn.Lock ()) { + conn.BeginTransaction (); + try { + action (this); + conn.Commit (); + } + catch (Exception) { + conn.Rollback (); + throw; + } + } + }); + } + + public Task RunInTransactionAsync(Action action) + { + return Task.Factory.StartNew(() => + { + var conn = this.GetConnection(); + using (conn.Lock()) + { + conn.BeginTransaction(); + try + { + action(conn); + conn.Commit(); + } + catch (Exception) + { + conn.Rollback(); + throw; + } + } + }); + } + + public AsyncTableQuery Table () + where T : new () + { + // + // This isn't async as the underlying connection doesn't go out to the database + // until the query is performed. The Async methods are on the query iteself. + // + var conn = GetConnection (); + return new AsyncTableQuery (conn.Table ()); + } + + public Task ExecuteScalarAsync (string sql, params object[] args) + { + return Task.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + var command = conn.CreateCommand (sql, args); + return command.ExecuteScalar (); + } + }); + } + + public Task> QueryAsync (string sql, params object[] args) + where T : new () + { + return Task>.Factory.StartNew (() => { + var conn = GetConnection (); + using (conn.Lock ()) { + return conn.Query (sql, args); + } + }); + } + } + + // + // TODO: Bind to AsyncConnection.GetConnection instead so that delayed + // execution can still work after a Pool.Reset. + // + public class AsyncTableQuery + where T : new () + { + TableQuery _innerQuery; + + public AsyncTableQuery (TableQuery innerQuery) + { + _innerQuery = innerQuery; + } + + public AsyncTableQuery Where (Expression> predExpr) + { + return new AsyncTableQuery (_innerQuery.Where (predExpr)); + } + + public AsyncTableQuery Skip (int n) + { + return new AsyncTableQuery (_innerQuery.Skip (n)); + } + + public AsyncTableQuery Take (int n) + { + return new AsyncTableQuery (_innerQuery.Take (n)); + } + + public AsyncTableQuery OrderBy (Expression> orderExpr) + { + return new AsyncTableQuery (_innerQuery.OrderBy (orderExpr)); + } + + public AsyncTableQuery OrderByDescending (Expression> orderExpr) + { + return new AsyncTableQuery (_innerQuery.OrderByDescending (orderExpr)); + } + + public Task> ToListAsync () + { + return Task.Factory.StartNew (() => { + using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { + return _innerQuery.ToList (); + } + }); + } + + public Task CountAsync () + { + return Task.Factory.StartNew (() => { + using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { + return _innerQuery.Count (); + } + }); + } + + public Task ElementAtAsync (int index) + { + return Task.Factory.StartNew (() => { + using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { + return _innerQuery.ElementAt (index); + } + }); + } + + public Task FirstAsync () + { + return Task.Factory.StartNew(() => { + using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { + return _innerQuery.First (); + } + }); + } + + public Task FirstOrDefaultAsync () + { + return Task.Factory.StartNew(() => { + using (((SQLiteConnectionWithLock)_innerQuery.Connection).Lock ()) { + return _innerQuery.FirstOrDefault (); + } + }); + } + } + + public class CreateTablesResult + { + public Dictionary Results { get; private set; } + + internal CreateTablesResult () + { + this.Results = new Dictionary (); + } + } + + class SQLiteConnectionPool + { + class Entry + { + public SQLiteConnectionString ConnectionString { get; private set; } + public SQLiteConnectionWithLock Connection { get; private set; } + + public Entry (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) + { + ConnectionString = connectionString; + Connection = new SQLiteConnectionWithLock (connectionString, openFlags); + } + + public void OnApplicationSuspended () + { + Connection.Dispose (); + Connection = null; + } + } + + readonly Dictionary _entries = new Dictionary (); + readonly object _entriesLock = new object (); + + static readonly SQLiteConnectionPool _shared = new SQLiteConnectionPool (); + + /// + /// Gets the singleton instance of the connection tool. + /// + public static SQLiteConnectionPool Shared + { + get + { + return _shared; + } + } + + public SQLiteConnectionWithLock GetConnection (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) + { + lock (_entriesLock) { + Entry entry; + string key = connectionString.ConnectionString; + + if (!_entries.TryGetValue (key, out entry)) { + entry = new Entry (connectionString, openFlags); + _entries[key] = entry; + } + + return entry.Connection; + } + } + + /// + /// Closes all connections managed by this pool. + /// + public void Reset () + { + lock (_entriesLock) { + foreach (var entry in _entries.Values) { + entry.OnApplicationSuspended (); + } + _entries.Clear (); + } + } + + /// + /// Call this method when the application is suspended. + /// + /// Behaviour here is to close any open connections. + public void ApplicationSuspended () + { + Reset (); + } + } + + class SQLiteConnectionWithLock : SQLiteConnection + { + readonly object _lockPoint = new object (); + + public SQLiteConnectionWithLock (SQLiteConnectionString connectionString, SQLiteOpenFlags openFlags) + : base (connectionString.DatabasePath, openFlags, connectionString.StoreDateTimeAsTicks) + { + } + + public IDisposable Lock () + { + return new LockWrapper (_lockPoint); + } + + private class LockWrapper : IDisposable + { + object _lockPoint; + + public LockWrapper (object lockPoint) + { + _lockPoint = lockPoint; + Monitor.Enter (_lockPoint); + } + + public void Dispose () + { + Monitor.Exit (_lockPoint); + } + } + } +} + diff --git a/NadekoBot/NadekoBot.cs b/WizBot/WizBot.cs similarity index 89% rename from NadekoBot/NadekoBot.cs rename to WizBot/WizBot.cs index ac61d96da..3db14ceb5 100644 --- a/NadekoBot/NadekoBot.cs +++ b/WizBot/WizBot.cs @@ -2,23 +2,26 @@ using Discord.Audio; using Discord.Commands; using Discord.Modules; -using NadekoBot.Classes.Help.Commands; -using NadekoBot.Classes.JSONModels; -using NadekoBot.Modules.Administration; -using NadekoBot.Modules.ClashOfClans; -using NadekoBot.Modules.Conversations; -using NadekoBot.Modules.Gambling; -using NadekoBot.Modules.Games; -using NadekoBot.Modules.Games.Commands; -using NadekoBot.Modules.Help; -using NadekoBot.Modules.Music; -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 WizBot.Classes.Help.Commands; +using WizBot.Classes.JSONModels; +using WizBot.Modules.Administration; +using WizBot.Modules.ClashOfClans; +using WizBot.Modules.Conversations; +using WizBot.Modules.CustomReactions; +using WizBot.Modules.Gambling; +using WizBot.Modules.Games; +using WizBot.Modules.Games.Commands; +using WizBot.Modules.Help; +#if !WIZBOT_RELEASE +using WizBot.Modules.Music; +#endif +using WizBot.Modules.NSFW; +using WizBot.Modules.Permissions; +using WizBot.Modules.Permissions.Classes; +using WizBot.Modules.Pokemon; +using WizBot.Modules.Searches; +using WizBot.Modules.Translator; +using WizBot.Modules.Trello; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -27,10 +30,11 @@ using System.Text; using System.Threading.Tasks; -namespace NadekoBot +namespace WizBot { - public class NadekoBot + public class WizBot { + public static string Title { get; set; } = Console.Title = "WizBot by Kwoth & Wizkiller96"; public static DiscordClient Client { get; private set; } public static Credentials Creds { get; set; } public static Configuration Config { get; set; } @@ -177,12 +181,15 @@ private static void Main() modules.Add(new Conversations(), "Conversations", ModuleFilter.None); modules.Add(new GamblingModule(), "Gambling", ModuleFilter.None); modules.Add(new GamesModule(), "Games", ModuleFilter.None); +#if !WIZBOT_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); @@ -198,7 +205,7 @@ private static void Main() await Client.Connect(Creds.Token).ConfigureAwait(false); IsBot = true; } - Console.WriteLine(NadekoBot.Client.CurrentUser.Id); + } catch (Exception ex) { @@ -211,9 +218,14 @@ private static void Main() return; } - //await Task.Delay(90000).ConfigureAwait(false); +#if !WIZBOT_RELEASE + await Task.Delay(100000).ConfigureAwait(false); +#else + await Task.Delay(1000).ConfigureAwait(false); +#endif + Console.WriteLine("-----------------"); - Console.WriteLine(await NadekoStats.Instance.GetStats().ConfigureAwait(false)); + Console.WriteLine(await WizStats.Instance.GetStats().ConfigureAwait(false)); Console.WriteLine("-----------------"); try @@ -235,7 +247,7 @@ private static void Main() e.Cancel = true; }; PermissionsHandler.Initialize(); - NadekoBot.Ready = true; + WizBot.Ready = true; }); Console.WriteLine("Exiting..."); Console.ReadKey(); @@ -259,7 +271,7 @@ private static async void Client_MessageReceived(object sender, MessageEventArgs if (ConfigHandler.IsBlackListed(e)) return; - if (!NadekoBot.Config.DontJoinServers && !IsBot) + if (!WizBot.Config.DontJoinServers && !IsBot) { try { @@ -277,7 +289,7 @@ private static async void Client_MessageReceived(object sender, MessageEventArgs } } - if (Config.ForwardMessages && !NadekoBot.Creds.OwnerIds.Contains(e.User.Id) && OwnerPrivateChannel != null) + if (Config.ForwardMessages && !WizBot.Creds.OwnerIds.Contains(e.User.Id) && OwnerPrivateChannel != null) await OwnerPrivateChannel.SendMessage(e.User + ": ```\n" + e.Message.Text + "\n```").ConfigureAwait(false); if (repliedRecently) return; diff --git a/NadekoBot/NadekoBot.csproj b/WizBot/WizBot.csproj similarity index 83% rename from NadekoBot/NadekoBot.csproj rename to WizBot/WizBot.csproj index 8660d1941..48b3690ea 100644 --- a/NadekoBot/NadekoBot.csproj +++ b/WizBot/WizBot.csproj @@ -1,482 +1,513 @@ - - - - - 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;__DEMO__,__DEMO_EXPERIMENTAL__ - prompt - 4 - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\PRIVATE\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - 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\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 - - - - - - - ..\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 - - - - - - - - - - - - False - Microsoft .NET Framework 4.5.2 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - PublicResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {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 - - - - - - - + + + + + Debug + AnyCPU + {27A886F5-CDDA-4F4A-81EE-6DAFCCE9DE46} + Exe + Properties + WizBot + WizBot + v4.5.2 + 512 + true + false + + + + C:\Users\Master\Desktop\WizBot\ + 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;__DEMO__,__DEMO_EXPERIMENTAL__ + prompt + 4 + true + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\PRIVATE\ + DEBUG;TRACE + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + true + + + bin\WizBotRelease\ + TRACE;WIZBOT_RELEASE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + true + + + + + ..\packages\Discord.Net.0.9.3\lib\net45\Discord.Net.dll + True + + + ..\packages\Discord.Net.Audio.0.9.3\lib\net45\Discord.Net.Audio.dll + True + + + ..\packages\Discord.Net.Commands.0.9.3\lib\net45\Discord.Net.Commands.dll + True + + + ..\packages\Discord.Net.Modules.0.9.3\lib\net45\Discord.Net.Modules.dll + True + + + ..\packages\VideoLibrary.1.3.3\lib\portable-net45+win+wpa81+MonoAndroid10+xamarinios10+MonoTouch10\libvideo.dll + True + + + ..\packages\Manatee.Json.4.0.1\lib\net45\Manatee.Json.dll + True + + + ..\packages\Manatee.StateMachine.1.1.2\lib\net45\Manatee.StateMachine.dll + True + + + ..\packages\Manatee.Trello.1.10.0\lib\net45\Manatee.Trello.dll + True + + + ..\packages\Manatee.Trello.ManateeJson.1.5.0\lib\net45\Manatee.Trello.ManateeJson.dll + True + + + ..\packages\Manatee.Trello.WebApi.1.0.4\lib\net45\Manatee.Trello.WebApi.dll + True + + + ..\packages\Newtonsoft.Json.8.0.4-beta1\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.dll + True + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Concurrent.dll + True + + + ..\packages\Nito.AsyncEx.3.0.1\lib\net45\Nito.AsyncEx.Enlightenment.dll + True + + + ..\packages\RestSharp.105.2.3\lib\net452\RestSharp.dll + True + + + False + lib\ScaredFingers.UnitsConversion.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 + + + + + + + ..\packages\WebSocket4Net.0.14.1\lib\net45\WebSocket4Net.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + False + Microsoft .NET Framework 4.5.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NadekoBot/_Models/DataModels/AnnouncementModel.cs b/WizBot/_Models/DataModels/AnnouncementModel.cs similarity index 87% rename from NadekoBot/_Models/DataModels/AnnouncementModel.cs rename to WizBot/_Models/DataModels/AnnouncementModel.cs index 534daf24b..49dbbd848 100644 --- a/NadekoBot/_Models/DataModels/AnnouncementModel.cs +++ b/WizBot/_Models/DataModels/AnnouncementModel.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace NadekoBot.DataModels +namespace WizBot.DataModels { internal class Announcement : IDataModel { @@ -15,5 +15,6 @@ internal class Announcement : IDataModel [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/WizBot/_Models/DataModels/CommandModel.cs similarity index 91% rename from NadekoBot/_Models/DataModels/CommandModel.cs rename to WizBot/_Models/DataModels/CommandModel.cs index 204ec0619..da8ccd03f 100644 --- a/NadekoBot/_Models/DataModels/CommandModel.cs +++ b/WizBot/_Models/DataModels/CommandModel.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels { +namespace WizBot.DataModels { internal class Command : IDataModel { public long UserId { get; set; } public string UserName { get; set; } diff --git a/NadekoBot/_Models/DataModels/CurrencyStateModel.cs b/WizBot/_Models/DataModels/CurrencyStateModel.cs similarity index 81% rename from NadekoBot/_Models/DataModels/CurrencyStateModel.cs rename to WizBot/_Models/DataModels/CurrencyStateModel.cs index 9c8b86714..d6dee097a 100644 --- a/NadekoBot/_Models/DataModels/CurrencyStateModel.cs +++ b/WizBot/_Models/DataModels/CurrencyStateModel.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels { +namespace WizBot.DataModels { internal class CurrencyState : IDataModel { public long Value { get; set; } [SQLite.Unique] diff --git a/NadekoBot/_Models/DataModels/CurrencyTransactionModel.cs b/WizBot/_Models/DataModels/CurrencyTransactionModel.cs similarity index 83% rename from NadekoBot/_Models/DataModels/CurrencyTransactionModel.cs rename to WizBot/_Models/DataModels/CurrencyTransactionModel.cs index 51fcccc3b..261bd92fb 100644 --- a/NadekoBot/_Models/DataModels/CurrencyTransactionModel.cs +++ b/WizBot/_Models/DataModels/CurrencyTransactionModel.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels { +namespace WizBot.DataModels { internal class CurrencyTransaction : IDataModel { public string Reason { get; set; } public int Value { get; set; } diff --git a/NadekoBot/_Models/DataModels/Donator.cs b/WizBot/_Models/DataModels/Donator.cs similarity index 83% rename from NadekoBot/_Models/DataModels/Donator.cs rename to WizBot/_Models/DataModels/Donator.cs index 483898ec9..04b996003 100644 --- a/NadekoBot/_Models/DataModels/Donator.cs +++ b/WizBot/_Models/DataModels/Donator.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels { +namespace WizBot.DataModels { internal class Donator : IDataModel { public long UserId { get; set; } public string UserName { get; set; } diff --git a/NadekoBot/_Models/DataModels/IDataModel.cs b/WizBot/_Models/DataModels/IDataModel.cs similarity index 90% rename from NadekoBot/_Models/DataModels/IDataModel.cs rename to WizBot/_Models/DataModels/IDataModel.cs index 90191474c..2a0cb18b5 100644 --- a/NadekoBot/_Models/DataModels/IDataModel.cs +++ b/WizBot/_Models/DataModels/IDataModel.cs @@ -1,7 +1,7 @@ using SQLite; using System; -namespace NadekoBot.DataModels +namespace WizBot.DataModels { internal abstract class IDataModel { diff --git a/NadekoBot/_Models/DataModels/MusicPlaylist.cs b/WizBot/_Models/DataModels/MusicPlaylist.cs similarity index 85% rename from NadekoBot/_Models/DataModels/MusicPlaylist.cs rename to WizBot/_Models/DataModels/MusicPlaylist.cs index 60973baa2..9011c6dc4 100644 --- a/NadekoBot/_Models/DataModels/MusicPlaylist.cs +++ b/WizBot/_Models/DataModels/MusicPlaylist.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels +namespace WizBot.DataModels { internal class MusicPlaylist : IDataModel { diff --git a/NadekoBot/_Models/DataModels/PlaylistSongInfo.cs b/WizBot/_Models/DataModels/PlaylistSongInfo.cs similarity index 83% rename from NadekoBot/_Models/DataModels/PlaylistSongInfo.cs rename to WizBot/_Models/DataModels/PlaylistSongInfo.cs index 849297819..6ba8d3b88 100644 --- a/NadekoBot/_Models/DataModels/PlaylistSongInfo.cs +++ b/WizBot/_Models/DataModels/PlaylistSongInfo.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels +namespace WizBot.DataModels { internal class PlaylistSongInfo : IDataModel { diff --git a/NadekoBot/_Models/DataModels/PokeTypes.cs b/WizBot/_Models/DataModels/PokeTypes.cs similarity index 89% rename from NadekoBot/_Models/DataModels/PokeTypes.cs rename to WizBot/_Models/DataModels/PokeTypes.cs index d0f538af1..2304c680f 100644 --- a/NadekoBot/_Models/DataModels/PokeTypes.cs +++ b/WizBot/_Models/DataModels/PokeTypes.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace NadekoBot.DataModels +namespace WizBot.DataModels { class UserPokeTypes : IDataModel { diff --git a/NadekoBot/_Models/DataModels/Reminder.cs b/WizBot/_Models/DataModels/Reminder.cs similarity index 91% rename from NadekoBot/_Models/DataModels/Reminder.cs rename to WizBot/_Models/DataModels/Reminder.cs index 043700fd9..143f66c85 100644 --- a/NadekoBot/_Models/DataModels/Reminder.cs +++ b/WizBot/_Models/DataModels/Reminder.cs @@ -1,6 +1,6 @@ using System; -namespace NadekoBot.DataModels +namespace WizBot.DataModels { class Reminder : IDataModel { diff --git a/NadekoBot/_Models/DataModels/RequestModel.cs b/WizBot/_Models/DataModels/RequestModel.cs similarity index 90% rename from NadekoBot/_Models/DataModels/RequestModel.cs rename to WizBot/_Models/DataModels/RequestModel.cs index 7b86198f2..360848737 100644 --- a/NadekoBot/_Models/DataModels/RequestModel.cs +++ b/WizBot/_Models/DataModels/RequestModel.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels { +namespace WizBot.DataModels { internal class Request : IDataModel { public string UserName { get; set; } public long UserId { get; set; } diff --git a/NadekoBot/_Models/DataModels/SongInfo.cs b/WizBot/_Models/DataModels/SongInfo.cs similarity index 91% rename from NadekoBot/_Models/DataModels/SongInfo.cs rename to WizBot/_Models/DataModels/SongInfo.cs index 6fe980ac6..5ae3cca35 100644 --- a/NadekoBot/_Models/DataModels/SongInfo.cs +++ b/WizBot/_Models/DataModels/SongInfo.cs @@ -1,6 +1,6 @@ using SQLite; -namespace NadekoBot.DataModels +namespace WizBot.DataModels { internal class SongInfo : IDataModel { diff --git a/NadekoBot/_Models/DataModels/StatsModel.cs b/WizBot/_Models/DataModels/StatsModel.cs similarity index 88% rename from NadekoBot/_Models/DataModels/StatsModel.cs rename to WizBot/_Models/DataModels/StatsModel.cs index 480b569fb..66c00d6de 100644 --- a/NadekoBot/_Models/DataModels/StatsModel.cs +++ b/WizBot/_Models/DataModels/StatsModel.cs @@ -1,6 +1,6 @@ using System; -namespace NadekoBot.DataModels { +namespace WizBot.DataModels { internal class Stats : IDataModel { public int ConnectedServers { get; set; } public int OnlineUsers { get; set; } diff --git a/NadekoBot/_Models/DataModels/TypingArticleModel.cs b/WizBot/_Models/DataModels/TypingArticleModel.cs similarity index 72% rename from NadekoBot/_Models/DataModels/TypingArticleModel.cs rename to WizBot/_Models/DataModels/TypingArticleModel.cs index 2557a51cb..9b1f4376e 100644 --- a/NadekoBot/_Models/DataModels/TypingArticleModel.cs +++ b/WizBot/_Models/DataModels/TypingArticleModel.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels { +namespace WizBot.DataModels { internal class TypingArticle : IDataModel { public string Text { get; set; } } diff --git a/NadekoBot/_Models/DataModels/UserQuoteModel.cs b/WizBot/_Models/DataModels/UserQuoteModel.cs similarity index 83% rename from NadekoBot/_Models/DataModels/UserQuoteModel.cs rename to WizBot/_Models/DataModels/UserQuoteModel.cs index 544252cc1..e0f1ad21b 100644 --- a/NadekoBot/_Models/DataModels/UserQuoteModel.cs +++ b/WizBot/_Models/DataModels/UserQuoteModel.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.DataModels { +namespace WizBot.DataModels { internal class UserQuote : IDataModel { public string UserName { get; set; } public string Keyword { get; set; } diff --git a/NadekoBot/_Models/JSONModels/AnimeResult.cs b/WizBot/_Models/JSONModels/AnimeResult.cs similarity index 93% rename from NadekoBot/_Models/JSONModels/AnimeResult.cs rename to WizBot/_Models/JSONModels/AnimeResult.cs index a092ae031..a6f5930a6 100644 --- a/NadekoBot/_Models/JSONModels/AnimeResult.cs +++ b/WizBot/_Models/JSONModels/AnimeResult.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Classes.JSONModels +namespace WizBot.Classes.JSONModels { public class AnimeResult { diff --git a/WizBot/_Models/JSONModels/Configuration.cs b/WizBot/_Models/JSONModels/Configuration.cs new file mode 100644 index 000000000..33e72a1fe --- /dev/null +++ b/WizBot/_Models/JSONModels/Configuration.cs @@ -0,0 +1,196 @@ +using Discord; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.IO; + +namespace WizBot.Classes.JSONModels +{ + public class Configuration + { + public bool DontJoinServers { get; set; } = false; + public bool ForwardMessages { get; set; } = true; + public bool IsRotatingStatus { get; set; } = false; + + [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%.**⏰❗"; + + public Dictionary> CustomReactions { get; set; } = 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% neko", new List() + { + "https://scontent.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/12750101_1313763215317390_1645972529_n.jpg?ig_cache_key=MTE4NzIzNDIzMzMxMTM3Njc0OQ%3D%3D.2.l", + "http://i117.photobucket.com/albums/o47/Nana-gloss/Neko/nekobible3.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/7a/9b/bd/7a9bbd1c3b16d2cfba0874974c9794bc.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/61/8b/b7/618bb7b7f61d2e3794846a6e6880ba47.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/bc/dc/4c/bcdc4cd8d0cc64a51eef1ee5c413cc72.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/ff/b2/43/ffb243bc5225426b9916a0fef21ed6ac.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/55/31/ac/5531aca0651dee8e60260617c6d28b39.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/89/0e/03/890e033cc3411f1b80d977aaffa81c98.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/48/ee/78/48ee78d5fe31fb9dd1d2bc32df4159d7.jpg" + } }, + {"%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" + + } } + }; + + 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 + }; + + + 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; } = "WizFlower"; + public string DMHelpString { get; set; } = "Type `-h` for help."; + } + + 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; } = "!m"; + 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 static class ConfigHandler + { + private static readonly object configLock = new object(); + public static void SaveConfig() + { + lock (configLock) + { + File.WriteAllText("data/config.json", JsonConvert.SerializeObject(WizBot.Config, Formatting.Indented)); + } + } + + 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) => WizBot.Config.ServerBlacklist.Contains(id); + + public static bool IsChannelBlacklisted(ulong id) => WizBot.Config.ChannelBlacklist.Contains(id); + + public static bool IsUserBlacklisted(ulong id) => WizBot.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}*"; + } +} \ No newline at end of file diff --git a/NadekoBot/_Models/JSONModels/LocalizedStrings.cs b/WizBot/_Models/JSONModels/LocalizedStrings.cs similarity index 91% rename from NadekoBot/_Models/JSONModels/LocalizedStrings.cs rename to WizBot/_Models/JSONModels/LocalizedStrings.cs index 861ebb69e..17b627ac4 100644 --- a/NadekoBot/_Models/JSONModels/LocalizedStrings.cs +++ b/WizBot/_Models/JSONModels/LocalizedStrings.cs @@ -1,10 +1,11 @@ using System.IO; -namespace NadekoBot.Classes.JSONModels { +namespace WizBot.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." + " I will eat you when I get my powers back.", + " You are a baka." }; public string[] Praises { get; set; } = { @@ -13,7 +14,8 @@ public class LocalizedStrings { " You did a good job.", " You did something nice.", " is awesome!", - " Wow." + " Wow.", + " You did well." }; public static string[] GetAvailableLocales() { diff --git a/NadekoBot/_Models/JSONModels/MagicItem.cs b/WizBot/_Models/JSONModels/MagicItem.cs similarity index 89% rename from NadekoBot/_Models/JSONModels/MagicItem.cs rename to WizBot/_Models/JSONModels/MagicItem.cs index 1ef3db1b2..ad3bb6e4e 100644 --- a/NadekoBot/_Models/JSONModels/MagicItem.cs +++ b/WizBot/_Models/JSONModels/MagicItem.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace NadekoBot.Classes.JSONModels +namespace WizBot.Classes.JSONModels { class MagicItem { diff --git a/NadekoBot/_Models/JSONModels/MangaResult.cs b/WizBot/_Models/JSONModels/MangaResult.cs similarity index 94% rename from NadekoBot/_Models/JSONModels/MangaResult.cs rename to WizBot/_Models/JSONModels/MangaResult.cs index b7cb9f731..2d923548d 100644 --- a/NadekoBot/_Models/JSONModels/MangaResult.cs +++ b/WizBot/_Models/JSONModels/MangaResult.cs @@ -1,4 +1,4 @@ -namespace NadekoBot.Classes.JSONModels +namespace WizBot.Classes.JSONModels { public class MangaResult { diff --git a/NadekoBot/_Models/JSONModels/PokemonType.cs b/WizBot/_Models/JSONModels/PokemonType.cs similarity index 95% rename from NadekoBot/_Models/JSONModels/PokemonType.cs rename to WizBot/_Models/JSONModels/PokemonType.cs index 02e50e2f6..1a31e3ae0 100644 --- a/NadekoBot/_Models/JSONModels/PokemonType.cs +++ b/WizBot/_Models/JSONModels/PokemonType.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace NadekoBot.Classes.JSONModels +namespace WizBot.Classes.JSONModels { public class PokemonType { diff --git a/WizBot/_Models/JSONModels/_JSONModels.cs b/WizBot/_Models/JSONModels/_JSONModels.cs new file mode 100644 index 000000000..4e5e3a1a5 --- /dev/null +++ b/WizBot/_Models/JSONModels/_JSONModels.cs @@ -0,0 +1,155 @@ +// ReSharper disable InconsistentNaming + +using System.Diagnostics; + +namespace WizBot.Classes.JSONModels +{ + public class Credentials + { + public string Username = "myemail@email.com"; + public string Password = "xxxxxxx"; + public string Token = ""; + public ulong BotId = 1231231231231; + public string GoogleAPIKey = ""; + public ulong[] OwnerIds = { 123123123123, 99272781513920512 }; + public string TrelloAppKey = ""; + public string SoundCloudClientID = ""; + public string MashapeKey = ""; + public string LOLAPIKey = ""; + public string CarbonKey = ""; + } + [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/WizBot/bin/Debug/Manatee.Json.xml b/WizBot/bin/Debug/Manatee.Json.xml new file mode 100644 index 000000000..2ccda5014 --- /dev/null +++ b/WizBot/bin/Debug/Manatee.Json.xml @@ -0,0 +1,3341 @@ + + + + Manatee.Json + + + + + Provides extension methods for s. + + + + + Returns a containing only the s of a specified type from a given . + + The array to search + The type of value to return + A containing only the s of a specified type + + + + Thrown when an input string contains a syntax error while parsing a , , or . + + + + + Gets the path up to the point at which the error was found. + + + + + Gets a message that describes the current exception. + + + The error message that explains the reason for the exception, or an empty string(""). + + 1 + + + + Thrown when a value is accessed via the incorrect type accessor. + + + + + The correct type for the that threw the exception. + + + + + The type requested. + + + + + Creates a new instance of this exception. + + + + + Represents a collection of JSON values. + + + A value can consist of a string, a numeric value, a boolean (true or false), a null placeholder, + a JSON array of values, or a nested JSON object. + + + + + Creates an empty instance of a JSON array. + + + + + Creates an instance of a JSON array and initializes it with the + supplied JSON values. + + + + + + Creates a string representation of the JSON data. + + The indention level for the array. + A string. + + + + Creates a string representation of the JSON data. + + A string. + + Passing the returned string back into the parser will result in a copy of + this Json array. + + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The to compare with the current . 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Provides extension methods for s. + + + + + Returns a or null if the key is not found or is not a . + + The to search + The key + A or null if the key is not found or is not a + + + + Returns a or null if the key is not found or is not a double. + + The to search + The key + A or null if the key is not found or is not a + + + + Returns a or null if the key is not found or is not a . + + The to search + The key + A or null if the key is not found or is not a + + + + Returns a or null if the key is not found or is not a . + + The to search + The key + A or null if the key is not found or is not a + + + + Returns a or null if the key is not found or is not a . + + The to search + The key + A or null if the key is not found or is not a + + + + Represents a collection of key:value pairs in a JSON structure. + + + A key is always represented as a string. A value can consist of a string, a numerical value, + a boolean (true or false), a null placeholder, a JSON array of values, or a nested JSON object. + + + + + Gets or sets the value associated with the specified key. + + The key of the value to get or set. + The value associated with the specified key. + + + + Creates an empty instance of a JSON object. + + + + + Creates an instance of a JSON object and initializes it with the + supplied JSON values. + + + + + + Creates a string representation of the JSON data. + + The indention level for the object. + A string. + + + + Adds the specified key and value to the dictionary. + + The key of the element to add. + The value of the element to add. The value can be null for reference types. + + + + Creates a string representation of the JSON data. + + A string. + + Passing the returned string back into the parser will result in a copy of + this JSON object. + + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The to compare with the current . 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Represents a JSON value. + + + A value can consist of a string, a numerical value, a boolean (true or false), a null + placeholder, a JSON array of values, or a nested JSON object. + + + + + Globally defined null-valued JSON value. + + + Use this when initializing a JSON object or array. + + + + + Accesses the as a boolean. + + + Thrown when this does not contain a boolean. + + + Setting the value as a boolean will automatically change the 's type and + discard the old data. + + + + + Accesses the as a string. + + + Thrown when this does not contain a string. + + + Setting the value as a string will automatically change the 's type and + discard the old data. + + + + + Accesses the as a numeric value. + + + Thrown when this does not contain a numeric value. + + + Setting the value as a numeric value will automatically change the 's type and + discard the old data. + + + + + Accesses the as a JSON object. + + + Thrown when this does not contain a Json object. + + + Setting the value as a JSON object will automatically change the 's type and + discard the old data. + + + + + Accesses the as a JSON array. + + + Thrown when this does not contain a Json array. + + + Setting the value as a JSON array will automatically change the 's type and + discard the old data. + + + + + Gets the value type of the existing data. + + + + + Creates a null . + + + + + Creates a from a boolean. + + + + + Creates a from a string. + + + + + Creates a from a numeric value. + + + + + Creates a from a JSON object. + + + + + Creates a from a JSON array. + + + + + Creates a string representation of the JSON data. + + The indention level for the value. + A string. + + + + Creates a string that represents this . + + A string representation of this . + + Passing the returned string back into the parser will result in a copy of + this . + + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The to compare with the current . 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Parses a containing a JSON value. + + the to parse. + The JSON value represented by the . + Thrown if is null. + Thrown if is empty or whitespace. + Thrown if contains invalid JSON syntax. + + + + Parses data from a containing a JSON value. + + the to parse. + The JSON value represented by the . + Thrown if is null. + Thrown if is at the end. + Thrown if contains invalid JSON syntax. + + + + Implicitly converts a into a . + + A . + A that represents the . + + This is useful when creating an initialized or . + + + JsonObject obj = new JsonObject{ + {"stringData", "string"}, + {"numberData", 10.6}, + {"boolData", true}, + {"arrayData", new JsonArray{false, "Array String", JsonValue.Null, 8e-4}}, + {"objectData", new JsonObject{ + {"stringData2", "another string"}, + {"moreBoolData", false}}}}; + + + + + Implicitly converts a into a . + + A . + A that represents the . + + This is useful when creating an initialized or . + + + JsonObject obj = new JsonObject{ + {"stringData", "string"}, + {"numberData", 10.6}, + {"boolData", true}, + {"arrayData", new JsonArray{false, "Array String", JsonValue.Null, 8e-4}}, + {"objectData", new JsonObject{ + {"stringData2", "another string"}, + {"moreBoolData", false}}}}; + + + + + Implicitly converts a into a . + + A . + A that represents the . + + This is useful when creating an initialized or . + + + JsonObject obj = new JsonObject{ + {"stringData", "string"}, + {"numberData", 10.6}, + {"boolData", true}, + {"arrayData", new JsonArray{false, "Array String", JsonValue.Null, 8e-4}}, + {"objectData", new JsonObject{ + {"stringData2", "another string"}, + {"moreBoolData", false}}}}; + + + + + Implicitly converts a into a . + + A JSON object. + A that represents the . + + This is useful when creating an initialized or . + + + JsonObject obj = new JsonObject{ + {"stringData", "string"}, + {"numberData", 10.6}, + {"boolData", true}, + {"arrayData", new JsonArray{false, "Array String", JsonValue.Null, 8e-4}}, + {"objectData", new JsonObject{ + {"stringData2", "another string"}, + {"moreBoolData", false}}}}; + + + + + Implicitly converts a into a . + + A JSON array. + A that represents the . + + This is useful when creating an initialized or . + + + JsonObject obj = new JsonObject{ + {"stringData", "string"}, + {"numberData", 10.6}, + {"boolData", true}, + {"arrayData", new JsonArray{false, "Array String", JsonValue.Null, 8e-4}}, + {"objectData", new JsonObject{ + {"stringData2", "another string"}, + {"moreBoolData", false}}}}; + + + + + + + + + + + + + + + + + + + Specifies various types of values for use in a JSON key:value pair. + + + + + Indicates that the Json key:value pair contains a numeric value (double). + + + + + Indicates that the Json key:value pair contains a string. + + + + + Indicates that the Json key:value pair contains a boolean value. + + + + + Indicates that the Json key:value pair contains a nested Json object. + + + + + Indicates that the Json key:value pair contains a Json array. + + + + + Indicates that the Json key:value pair contains a null value. + + + + + These extension methods cover LINQ compatibility. + + + + + Converts an returned from a LINQ query back into + a . + + An + An equivalent + + + + Converts an of returned from a + LINQ query back into a . + + An of + An equivalent + + + + Converts a collection of strings to a . + + A collection of strings + A containing the strings + + + + Converts a collection of bools to a . + + A collection of booleans + A containing the booleans + + + + Converts a collection of to a . + + A collection of + A containing the + + + + Converts a collection of s to a . + + A collection of s + A containing the s + + + + Converts a collection of s to a . + + A collection of s + A containing the s + + + + Converts a collection of doubles to a . + + A collection of doubles + A containing the doubles + + + + Converts a collection of doubles to a . + + A collection of doubles + A containing the doubles + + + + Serializes a collection of objects which implement to a of equivalent JsonValues. + + A collection of equivalent s + The instance to use for additional + serialization of values. + A containing the equivalent JsonValues + + + + Converts an of returned from a + LINQ query back into a . + + An of + The instance to use for additional + serialization of values. + An equivalent + + + + Deserializes a collection of s to an of the objects. + + The type of object contained in the collection + The collection of s + The instance to use for additional + serialization of values. + A collection of the deserialized objects + + + + Deserializes a to its equivalent object. + + The type of object + The to deserialize + The instance to use for additional + serialization of values. + A collection of the deserialized objects + + + + Provides primary functionality for JSON Path objects. + + + + + Finalizes memory management responsibilities. + + + + + Parses a containing a JSON path. + + the to parse. + The JSON path represented by the . + Thrown if is null. + Thrown if is empty or whitespace. + Thrown if contains invalid JSON path syntax. + + + + Evaluates a JSON value using the path. + + The to evaulate. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Serves as a stand-in for JsonArray in Path Expressions. + + + + + Provides extension methods which can be used within array and search JSON Path queries. + + + + + Specifies the length of a . + + The length of the array. + + + + Determines if an object contains a property or, if its value is a boolean, whether the value is true. + + The name of the property. + true if the value is an object and contains key or if its value is true; otherwise false. + + + + Determines if an object contains a property containing a number and retrieves its value. + + The name of the property. + The value if the property exists and is a number; otherwise null. + + + + Determines if an object contains a property containing a number and retrieves its value. + + The index to retreive. + The value if the property exists and is a number; otherwise null. + + + + Thrown when an input string contains a syntax error while parsing a . + + + + + Gets the path up to the point at which the error was found. + + + + + Gets a message that describes the current exception. + + + The error message that explains the reason for the exception, or an empty string(""). + + 1 + + + + Serves as a stand-in for JsonValue in Path Expressions. + + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The to compare with the current . 2 + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The to compare with the current . 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Provides methods to be used when working with JSON Paths. + + + + + Creates a new object which starts by specifying an array length. + + A new . + + + + Creates a new object which starts by specifying an object property. + + The name to follow. + A new . + If is "length", operates as + + + + Creates a new object which starts by including all object properties. + + A new . + + + + Creates a new object which starts by searching for all values. + + A new . + + + + Creates a new object which starts by searching for an object property. + + The name to search for. + A new . + If is "length", operates as + + + + Creates a new object which starts by searching for array lengths. + + A new . + + + + Appends a by including all array values. + + The new . + + + + Appends a by specifying a series of array indicies. + + The indices of the s to include. + The new . + + + + Appends a by specifying a series of array indicies using array slice notation. + + The start index of the s to include. + The end index of the s to include. + The index interval of the s to include. + The new . + The format for the array slice is [start:end:step]. All parameters are individually optional, + however either the start or end must be defines. Negative values for start and end indicate that the + iterator should begin counting from the end of the array. + + + + Appends a by specifying an expression which evaluates to the index to include. + + The expression. + The new . + + + + Appends a by specifying a predicate expression which filters the values. + + The predicate expression + The new . + + + + Appends a by including all array values. + + The new . + + + + Appends a by specifying a series of array indicies. + + The indices of the s to include. + The new . + + + + Appends a by specifying a series of array indicies using array slice notation. + + The start index of the s to include. + The end index of the s to include. + The index interval of the s to include. + The new . + The format for the array slice is [start:end:step]. All parameters are individually optional, + however either the start or end must be defines. Negative values for start and end indicate that the + iterator should begin counting from the end of the array. + + + + Appends a by specifying an expression which evaluates to the index to include. + + The expression. + The new . + + + + Appends a by specifying a predicate expression which filters the values. + + The predicate expression + The new . + + + + Appends a by specifying an array length. + + The to extend. + The new . + + + + Appends a by specifying an object property. + + The to extend. + The name to follow. + The new . + If is "length", operates as + + + + Appends a by including all object properties. + + The to extend. + The new . + + + + Appends a by searching for all values. + + The to extend. + The new . + + + + Appends a by searching for an object property. + + The to extend. + The name to follow. + The new . + If is "length", operates as + + + + Appends a by searching for array lengths. + + The to extend. + The new . + + + + Appends a by including all array values. + + The to extend. + The new . + + + + Appends a by specifying a series of array indicies. + + The to extend. + The indices of the s to include. + The new . + + + + Appends a by specifying a series of array indicies using array slice notation. + + The to extend. + The start index of the s to include. + The end index of the s to include. + The index interval of the s to include. + The new . + The format for the array slice is [start:end:step]. All parameters are individually optional, + however either the start or end must be defines. Negative values for start and end indicate that the + iterator should begin counting from the end of the array. + + + + Appends a by specifying an expression which evaluates to the index to include. + + The to extend. + The expression. + The new . + + + + Appends a by specifying a predicate expression which filters the values. + + The to extend. + The predicate expression + The new . + + + + Provides extension methods which can be used within array and search JSON Path queries. + + + + + Specifies the length of a . + + The array. + The length of the array. + + + + Specifies the length of a . + + The array. + The length of the array. + + + + Determines if an object contains a property or, if its value is a boolean, whether the value is true. + + The value. + The name of the property. + true if the value is an object and contains key or if its value is true; otherwise false. + + + + Determines if an object contains a property containing a number and retrieves its value. + + The value. + The name of the property. + The value if the property exists and is a number; otherwise null. + + + + Determines if an object contains a property containing a number and retrieves its value. + + The value. + The index to retreive. + The value if the property exists and is a number; otherwise null. + + + + Gets the index of a value within an array. + + The value. + The query. + The index of the requested value or -1 if the value does not exist. + + + + Defines additional properties for ObjectSchema. + + + + + Allows any additional property to be added to the schema. + + + + + Prohibits additional properties in the schema. + + + + + Defines a schema to which any additional properties must validate. + + Thrown when attempting to set the definition + of one of the static fields. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The object to compare with the current object. 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Used to define a collection of schema conditions, all of which must be satisfied. + + + + + A collection of required schema which must be satisfied. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Used to define a collection of schema conditions, any number of which may + be satisfied. + + + + + A collection of schema options. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a schema which expects an array. + + + + + Defines a collection of schema type definitions. + + + + + Gets and sets a minimum number of items required for the array. + + + + + Defines a maximum number of items required for the array. + + + + + Defines the schema for the items contained in the array. + + + + + Defines whether the array should contain only unique items. + + + + + Creates a new instance of the class. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a schema which expects an boolean value. + + + + + Creates a new instance of the class. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Converts an object to a . + + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Defines a schema which expects one of an explicit list of values. + + + + + A collection of acceptable values. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a single schema enumeration value. + + + + + Creates a new instance of the class. + + The value. + + + + Validates a against the schema. + + A + The results of the validation. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Defines a type for all schema to implement. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + The results of the validation. + + + + Defines a schema which expects an integer. + + + + + Defines a minimum acceptable value. + + + + + Defines a maximum acceptable value; + + + + + Defines whether the minimum value is itself acceptable. + + + + + Defines whether the maximum value is itself acceptable. + + + + + Creates a new instance of the class. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Provides base functionality for the basic implementations.S + + + + + Defines an empty Schema. Useful for specifying that any schema is valid. + + + + + Defines the Draft-04 Schema as presented at http://json-schema.org/draft-04/schema# + + + + + The JSON Schema type which defines this schema. + + + + + The default value for this schema. + + + The default value is defined as a JSON value which may need to be deserialized + to a .Net data structure. + + + + + Used to specify which this schema defines. + + + + + Defines a title for this schema. + + + + + Defines a description for this schema. + + + + + Creates a new instance of the indicated type. + + The JSON Schema type which defines this schema. + Thrown when is null. + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The object to compare with the current object. 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines methods to build schema objects. + + + + + Returns + + + + + + + Creates a schema object from its JSON representation. + + A JSON object. + A schema object + + + + Builds a implementation which can validate JSON for a given type. + + The type to convert to a schema. + The schema object. + + + + Builds a implementation which can validate JSON for a given type. + + The type to convert to a schema. + The schema object. + + + + Defines a single property within a schema. + + + + + Defines the name of the property. + + + + + Defines a schema used to represent the type of this property. + + + + + Defines whether this property is required. + + + + + Creates a new instance of the class. + + The name of the type. + Thrown if is null, empty, or whitespace. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The object to compare with the current object. 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a collection of properties within a schema. + + + + + Retrieves a schema property by name. + + The name of the property. + The requested property or null if it does not exist. + + + + Defines a reference to a schema. + + + + + Defines a reference to the root schema. + + + + + Defines the reference in respect to the root schema. + + + + + Exposes the schema at the references location. + + + The method must first be called. + + + + + Creates a new instance of the class. + + The relative (internal) or absolute (URI) path to the referenced type definition. + Thrown when is nulll, empty, or whitespace. + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a single type definition within a schema. + + + + + Defines the array type. + + + + + Defines the boolean type. + + + + + Defines the integer type. + + + + + Defines the null type. + + + + + Defines the number type. + + + + + Defines the object type. + + + + + Defines the string type. + + + + + Defines the name of the type. + + + + + Defines a schema used to define the type. + + Thrown when attempting to set the definition + of one of the static fields. + + + + Creates a new instance of the type. + + The name of the type. + Thrown if is null, empty, or whitespace. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The object to compare with the current object. 2 + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The object to compare with the current object. 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a collection of type definitions within a schema. + + + + + Retrieves a schema type definition by name. + + The name of the type definition. + The requested type definition or null if it does not exist. + + + + Provides a mechanism to define a mutli-type schema. + + + + + Creates an instance of the class. + + The collection of objects. + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Used to define a collection of schema conditions, none of which may be satisfied. + + + + + A collection of schema which must not be satisfied. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a schema which expects a null value. + + + + + Creates an instance of the class. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a schema which expects a number. + + + + + Defines a minimum acceptable value. + + + + + Defines a maximum acceptable value; + + + + + Defines whether the minimum value is itself acceptable. + + + + + Defines whether the maximum value is itself acceptable. + + + + + Creates a new instance of the class. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Defines a schema which expects an object. + + + + + Used to specify a schema which contains the definitions used by this schema. + + + if left null, the default of http://json-schema.org/draft-04/schema# is used. + + + + + Defines a collection of schema type definitions. + + + + + Defines a collection of properties expected by this schema. + + + + + Defines any additional properties to be expected by this schema. + + + + + Defines additional properties based on regular expression matching of the property name. + + + + + Defines property dependencies. + + + + + Creates a new instance of the class. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Used to define a collection of schema conditions, exactly one of which must + be satisfied. + + + + + A collection of schema options. + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Represents a single schema validation error. + + + + + The property or property path which failed validation. + + + + + A message indicating the failure. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Contains the results of schema validation. + + + + + Gets whether the validation was successful. + + + + + Gets a collection of any errors which may have occurred during validation. + + + + + Defines various string formatting types used for StringSchema validation. + + + + + Defines a date/time format via + + + + + Defines an email address format. + + + From http://www.regular-expressions.info/email.html + + + + + Defines a host name format. + + + + + Defines an IPV4 address format. + + + + + Defines an IPV6 format. + + + + + Defines a regular expression format. + + + + + Defines a URI format via + + + + + A string key which specifies this string format. + + + + + Validates a value to the specified format. + + The value to validate. + True if the value is valid, otherwise false. + + + + Gets a object based on a format key. + + The predefined key for the format. + A object, or null if none exists for the key. + + + + Defines a schema which expects a string. + + + + + Defines a required format for the string. + + + + + Defines a minimum acceptable length. + + + + + Defines a maximum acceptable length. + + + + + Defines a pattern for to which the value must adhere. + + + + + Creates a new instance of the class + + + + + Validates a against the schema. + + A + The root schema serialized to a . Used internally for resolving references. + True if the passes validation; otherwise false. + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + Available formatting options for serializing DateTime objects. + + + + + Output conforms to ISO 8601 formatting: YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45+01:00) + + + + + Output is a string in the format "/Date([ms])/", where [ms] is the number of milliseconds + since January 1, 1970 UTC. + + + + + Output is a numeric value representing the number of milliseconds since January 1, 1970 UTC. + + + + + Output is formatted using the property. + + + + + Enumerates serialization formats for enumerations. + + + + + Instructs the serializer to convert enumeration values to their numeric + counterparts. + + + + + Instructs the serializer to convert enumeration values to their string + counterparts. + + + This option will use the Description attribute if it is present. If the + enumeration is marked with the flags attribute, the string representation + will consist of a comma-delimited list of names. Whenever a value is + passed which does not have a named counterpart, the numeric value will + be used. + + + + + Provides implementers the option to set a preferred method for serialization. + + + + + Builds an object from a . + + The representation of the object. + The instance to use for additional + serialization of values. + + + + Converts an object to a . + + The instance to use for additional + serialization of values. + The representation of the object. + + + + Enumeration of behavior options for the deserializer when a JSON structure is passed which + contains invalid property keys. + + + + + Deserializer ignores the invalid property keys. + + + + + Deserializer will throw an exception when an invalid property key is found. + + + + + Defines methods required to resolved instances for deserialization. + + + + + Resolves an instance of the given type. + + The type to resolve. + An instance of the type requested. + + + + Resolves an instance of the given type. + + The type to resolve. + An instance of the type requested. + + + + Applied to properties to indicate that they are not to be serialized. + + + + + Allows the user to specify how a property is mapped during serialization. + + + + + Specifies the key in the JSON object which maps to the property to which + this attribute is applied. + + + + + Creates a new instance fo the class. + + The JSON object key. + + + + Provides an interface to map abstract and interface types to + concrete types for object instantiation during deserialization. + + + + + Applies a mapping from an abstraction to a concrete type. + + The abstract type. + The concrete type. + The mapping behavior. + Thrown if TConcrete is an + abstract class or an interface. + + + + Applies a mapping from an open generic abstraction to an open generic concrete type. + + The abstract type. + The concrete type. + The mapping behavior. + Thrown if is an + abstract class or an interface or if does not inherit + from . + + + + Removes a previously-assigned mapping. + + The type to remove. + Optionally removes mappings of base and related interface types. + + + + Retrieves the map setting for an abstraction type. + + The abstraction type. + The mapped type if a mapping exists; otherwise the abstraction type. + + + + Manages methods for serializing object types which do not implement and + cannot be automatically serialized. + + + + + Declares the required signature for a serializer method. + + The type which the method serializes. + The object to be serialized. + The serializer to be used. + The JSON representation of the object. + + + + Declares the required signature for a deserializer method. + + The type which the method deserializes. + The JSON representation of the object. + The serializer to be used. + The deserialized object. + + + + Registers an encode/decode method pair for a specific type. + + The type. + The serializer method. + The deserializer method. + Thrown if either, but not both, + or is null. + + + + Gets whether a given type has been entered into the registry. + + The type. + True if an entry exists for the type; otherwise false. + + + + Gets whether a given type has been entered into the registry. + + The type. + True if an entry exists for the type; otherwise false. + + + + Serializes and deserializes objects and types to and from JSON structures. + + + + + Gets or sets a set of options for the serializer. + + + + + Serializes an object to a JSON structure. + + The type of the object to serialize. + The object to serialize. + The JSON representation of the object. + + + + Serializes the public static properties of a type to a JSON structure. + + The type to serialize. + The JSON representation of the type. + + + + Generates a template JSON inserting default values. + + + + + + + Deserializes a JSON structure to an object of the appropriate type. + + The type of the object that the JSON structure represents. + The JSON representation of the object. + The deserialized object. + Optionally thrown during automatic + deserialization when the JSON contains a property which is not defined by the requested + type. + + + + Deserializes a JSON structure to the public static properties of a type. + + The type to deserialize. + The JSON representation of the type. + Optionally thrown during automatic + deserialization when the JSON contains a property which is not defined by the requested + type. + + + + Represents a set of behavior options for the object. + + + + + Default options used by the serializer. + + + + + Gets and sets whether the serializer encodes default values for properties. + + + Setting to 'true' may significantly increase the size of the JSON structure. + + + + + Gets and sets the behavior of the deserializer when a JSON structure is passed which + contains invalid property keys. + + + + + Gets and sets the format for serialization using the default serializer methods. + + + If the entry for DateTime has been changed to custom + methods, this property will have no effect. + + + + + Gets and sets a custom serialization format for . + + + + + Gets and sets the format for enumeration serialization using the default serializer methods. + + + If an entry has been made in for the specific type, + this property will have no effect. + + + + + Gets and sets a separator to be used when serializing enumerations marked with the . + + + + + Gets and sets whether the serializer considers case for properties while deserializing. + + + This only affects automatic serialization. + + + + + Gets and sets whether the serializer always includes the type name while serializing. + + + This only affects automatic serialization. + + + + + Gets and sets which properties are automatically included while serializing. + + + + + Gets and sets an implementation for instantiating objects while deserializing. + + + + + Gets and sets whether public fields should be serialized during autoserialization. + + + + + Creates a new instance of with default options. + + + + + Creates a new instance of by copying an existing + instance. + + The instance to copy. + + + + Thrown when an abstract or interface type is mapped to another abstract or interface type. + + + + + Thrown when an abstract or interface type is mapped to another abstract or interface type. + + The type being mapped from. + The type being mapped to. + + + + Creates a new instance of the object. + + + + + Describes mapping behaviors for mapping abstraction types in the serializer. + + + + + Specifies that no additional mappings will be made. + + + + + Specifies that any unmapped base classes and interfaces will be mapped. + + + + + Specifies that all base classes and interfaces will be mapped, + overriding any existing mappings. + + + + + Enumerates the types of properties which are automatically serialized. + + + + + Indicates that read/write properties will be serialized. + + + + + Indicates that read-only properties will be serialized. + + + + + Optionally thrown when deserializing and the JSON structure contains property names + which are not valid for the type requested. + + + + + Gets the type. + + + + + Gets the portion of the JSON structure which contain the invalid properties. + + + + + Initializes a new instance of the class. + + The type. + The invalid JSON structure. + + + + Thrown when a type cannot be instantiated. + + + + + Creates a new instance of the class. + + The type which could not be instantiated. + + + + Enumerates serialization behaviors for saving type names. + + + + + Serializes the type name as necessary. + + + + + Always serializes the type name. + + + + + Never serializes the type name. + + + + + Thrown when + is passed one method and a null. + + + + + Gets the type. + + + + + Initializes a new instance of the class. + + The type. + + + + Performs transformations between JSON schemas. + + + + + Performs transformations between JSON schemas. + + The source JSON. + A template defining the transformation. + The transformed JSON. + + + + Contains functionality to map JSON values to XML constructs. + + + + + Converts a to an XElement + + A . + The key to be used as a top-level element name. + An representation of the . + + The 'key' parameter may be null only when the underlying JSON is an + object which contains a single key/value pair. + + Thrown if is null, empty, or whitespace + and is not a non-empty . + + + + Converts an to a . + + An . + The representation of the . + + + + Converts an to a . + + A collection of objects. + A single which represents the list of objects. + Thrown if an error occurs while attempting to convert an array of elements. + + + + Converts an to an . + + An . + The construct of the . + Provided for convenience. + + + + Converts an to an . + + An . + The construct of the . + Provided for convenience. + + + + Indicates that the marked method builds string by format pattern and (optional) arguments. + Parameter, which contains format string, should be given in constructor. The format string + should be in -like form. + + + [StringFormatMethod("message")] + void ShowError(string message, params object[] args) { /* do something */ } + + void Foo() { + ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + } + + + + + Specifies which parameter of an annotated method should be treated as format-string + + + + diff --git a/WizBot/bin/Debug/Manatee.StateMachine.xml b/WizBot/bin/Debug/Manatee.StateMachine.xml new file mode 100644 index 000000000..80ec4e134 --- /dev/null +++ b/WizBot/bin/Debug/Manatee.StateMachine.xml @@ -0,0 +1,272 @@ + + + + Manatee.StateMachine + + + + + Thrown when no action as been defined for the the given input in the current state. + + The object type of the state. + The object type of the input. + + + + Creates a new instance of this exception. + + + + + Creates a new instance of this exception with the default message. + + + + + Creates a new instance of this exception with a given message. + + + + + Creates a new instance of this exception with the a given message and inner exception. + + + + + Creates a new instance of this exception with serialization and context information. + + The serialization information. + The context information. + + + + The state of the state machine. + + + + + The input for the state. + + + + + Thrown when the current state is not set up to process the given input. + + The object type of the state. + The object type of the input. + + + + Creates a new instance of this exception. + + + + + Creates a new instance of this exception with the default message. + + + + + Creates a new instance of this exception with a given message. + + + + + Creates a new instance of this exception with the a given message and inner exception. + + + + + Creates a new instance of this exception with serialization and context information. + + The serialization information. + The context information. + + + + The state of the state machine. + + + + + The input that the state does not recognize. + + + + + Thrown when the specified state is not set up in the state machine. + + The object type of the state. + + + + Creates a new instance of this exception. + + + + + Creates a new instance of this exception with the default message. + + + + + Creates a new instance of this exception with a given message. + + + + + Creates a new instance of this exception with the a given message and inner exception. + + + + + Creates a new instance of this exception with serialization and context information. + + The serialization information. + The context information. + + + + The state that the state machine does not recognize. + + + + + Represents a stream of input parameters for a StateMachine object. Functions as a queue, + but can be reset to the beginning of the collection. + + + The object type used for the inputs in the StateMachine. + + + + + A default constructor. + + + + + Resets the enumerator index to the start. + + + + + Retrieves the next input object. + + + The next input object. + + + + + Gets whether the current index is at the end of the stream. + + + + + Represents an extensible generic state machine. + + The object type to be used for states. + The object type to be used for inputs. + + + + Creates a new StateMachine object. + + + + + Runs the state machine. + + The object which created the StateMachine. + The state from which to start. + The input stream. + + Thrown when the input stream contains an input that the current state does + not recognize. + + + Thrown when attempting to get a value for an input that the given state does + not recognize. + + + Thrown when attempting to get a value for a state that the state machine + does not recognize. + + + + + Disassociates an object from this StateMachine, allowing it to remove + all associated states. + + + + + + Gets the list of states. + + + + + Gets the list of all Inputs for all states. + + + + + Gets the list of all actions for all state-input combinations. + + + + + Gets and sets a custom update function. + + + The update function is called before each input is retrieved from + the input stream. It is typically used to update the input stream, + but can also include any code that needs to be executed for each + state-input evaluation. + + + + + Provides an interface for getting and setting actions for state-input + combinations. + + The state for which to get/set the action. + The input for which to get/set the action. + + The action for the given state-input combination. Throws an exception if + not set. + + + The setter automatically adds the state and input to the States and + Inputs lists. + + + Thrown when attempting to get a value for an input that the given state does + not recognize. + + + Thrown when attempting to get a value for a state that the state machine + does not recognize. + + + + + Provides a method template for a state-input action. + + The object which created the StateMachine. + The input that triggered the function call. + + The next state for this StateMachine object. + + + + + Provides a method template for an action to be called before each iteration + of the state machine. + + The object which created the StateMachine. + + + diff --git a/WizBot/bin/Debug/Manatee.Trello.ManateeJson.xml b/WizBot/bin/Debug/Manatee.Trello.ManateeJson.xml new file mode 100644 index 000000000..9c006f785 --- /dev/null +++ b/WizBot/bin/Debug/Manatee.Trello.ManateeJson.xml @@ -0,0 +1,53 @@ + + + + Manatee.Trello.ManateeJson + + + + + Creates instances of JSON interfaces. + + + + + Creates an instance of the requested JSON interface. + + The type to create. + An instance of the requested type. + + + + Wrapper class for the Manatee.Json.Serializer for use with RestSharp. + + + + + Creates and initializes a new instance of the ManateeJsonSerializer class. + + + + + Serializes an object to JSON. + + The object to serialize. + An equivalent JSON string. + + + + Attempts to deserialize a RESTful response to the indicated type. + + The type of object expected. + The response object which contains the JSON to deserialize. + The requested object, if JSON is valid; null otherwise. + + + + Attempts to deserialize a RESTful response to the indicated type. + + The type of object expected. + A string which contains the JSON to deserialize. + The requested object, if JSON is valid; null otherwise. + + + diff --git a/WizBot/bin/Debug/Manatee.Trello.WebApi.xml b/WizBot/bin/Debug/Manatee.Trello.WebApi.xml new file mode 100644 index 000000000..083d71be3 --- /dev/null +++ b/WizBot/bin/Debug/Manatee.Trello.WebApi.xml @@ -0,0 +1,13 @@ + + + + Manatee.Trello.WebApi + + + + + Implements IRestRequestProvider for WebApi. + + + + diff --git a/WizBot/bin/Debug/Manatee.Trello.xml b/WizBot/bin/Debug/Manatee.Trello.xml new file mode 100644 index 000000000..3611d7aaf --- /dev/null +++ b/WizBot/bin/Debug/Manatee.Trello.xml @@ -0,0 +1,6481 @@ + + + + Manatee.Trello + + + + + Represents an action performed on Trello objects. + + + + + Gets the creation date of the action. + + + + + Gets the member who performed the action. + + + + + Gets any data associated with the action. + + + + + Gets the date and time at which the action was performed. + + + + + Gets the action's ID. + + + + + Gets the type of action. + + + + + Raised when data on the action is updated. + + + + + Creates a new object. + + The action's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + + + Deletes the card. + + + This permanently deletes the card from Trello's server, however, this object will + remain in memory and all properties will remain accessible. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Represents a background image for a board. + + + + + The standard blue board background. + + + + + The standard orange board background. + + + + + The standard green board background. + + + + + The standard red board background. + + + + + The standard purple board background. + + + + + The standard pink board background. + + + + + The standard bright green board background. + + + + + The standard light blue board background. + + + + + The standard grey board background. + + + + + Gets the color of a stock solid-color background. + + + + + Gets the background's ID. + + + + + Gets the image of a background. + + + + + Gets whether the image is tiled when displayed. + + + + + Gets a collections of scaled background images. + + + + + Enumerates the filter options for board collections. Can be combined with the bitwise-OR operator. + + + + + Filters to boards that only members can access. + + + + + Filters to boards that only organization members can access. + + + + + Filters to boards that are publicly accessible. + + + + + Filters to open boards. + + + + + Filters to closed boards. + + + + + Filters to pinned boards. + + + + + Filters to unpinned boards. + + + + + Filters to starred boards. + + + + + Indicates that all boards should be returned. + + + + + Enumerates the filter options for organization collections. + + + + + Filters to organizations that only members can access. + + + + + Filters to organizations that are publicly accessible. + + + + + Enumerates the filter options for membership collections. + + + + + Filters to only the owner of the user token. + + + Get the board/organization membership information in addition to the member's profile. + + + + + Filters to only normal members. + + + + + Filters to only admins. + + + + + Filters to only active members. + + + + + Filters to only deactivated members. + + + + + Filters to all members. + + + + + Enumerates the filter options for member collections. + + + + + Filters to only normal members. + + + + + Filters to only admins. + + + + + Filters to only owners. + + + Per @doug at Trello regarding == : "Turns out owners was once used by the iOS app and we only have it there for backwards compatibility. They are the same." + + + + + Enumerates the filter options for list collections. + + + + + Filters to only unarchived lists. + + + + + Filters to only archived lists. + + + + + Indicates that all lists should be returned. + + + + + Enumerates the filter options for card collections. + + + + + Filters to only cards that are visible (not archived or in lists which are archived). + + + + + Filters to only unarchived cards. + + + + + Filters to only archived cards. + + + + + Indicates that all cards should be returned. + + + + + Extension methods for various collection types. + + + + + Filters a for a given . + + The + The new by which to filter. Can be combined using the bitwise OR operator. + The filtered collection. + Thrown when is included in the filter. + The new filter parameter will function as an OR parameter. + + + + Filters a for a given s. + + The + The new s by which to filter. + The filtered collection. + Thrown when is included in the filter. + The new filter parameters will function as OR parameters. + + + + + + The + The desired start date. + The desired end date. + Thrown when is included in the filter. + The filtered collection. + + + + Filters a for a given . + + The + The new by which to filter. Can be combined using the bitwise OR operator. + The filtered collection. + + + + Filters a for a given . + + The + The new by which to filter. Can be combined using the bitwise OR operator. + The filtered collection. + The new filter parameter will function as an OR parameter. + + + + Filters a for a given s. + + The + The new s by which to filter. + The filtered collection. + The new filter parameters will function as OR parameters. + + + + Filters a for a given . + + The + The new by which to filter. + The filtered collection. + + + + Filters a for a given . + + The + The new by which to filter. + The filtered collection. + + + + Filters a for a given . + + The + The new by which to filter. + The filtered collection. + The new filter parameter will function as an OR parameter. + + + + Filters a for a given s. + + The + The new s by which to filter. + The filtered collection. + The new filter parameters will function as OR parameters. + + + + Filters a for a given . + + The + The new by which to filter. Can be combined using the bitwise OR operator. + The filtered collection. + The new filter parameter will function as an OR parameter. + + + + Filters a for a given s. + + The + The new s by which to filter. + The filtered collection. + The new filter parameters will function as OR parameters. + + + + Filters a for a given . + + The + The new by which to filter. + The filtered collection. + + + + Filters a for a given . + + The + The new by which to filter. Can be combined using the bitwise OR operator. + The filtered collection. + The new filter parameter will function as an OR parameter. + + + + Filters a for a given s. + + The + The new s by which to filter. + The filtered collection. + The new filter parameters will function as OR parameters. + + + + Limits a to a specified count of items. + + The + The limit. + The limited collection. + + + + Limits a to a specified count of items. + + The + The limit. + The limited collection. + + + + Limits a to a specified count of items. + + The + The limit. + The limited collection. + + + + Limits a to a specified count of items. + + The + The limit. + The limited collection. + + + + Limits a to a specified count of items. + + The + The limit. + The limited collection. + + + + Limits a to a specified count of items. + + The + The limit. + The limited collection. + + + + Provides extension methods which allow fluent creation of search parameters. + + + + + Creates a new appending a text search parameter. + + The current search + The text to search for. + A new parameter list. + + + + Creates a new appending a text search parameter specific to card names. + + The current search + The text to search for. + A new parameter list. + + + + Creates a new appending a text search parameter specific to card descriptions. + + The current search + The text to search for. + A new parameter list. + + + + Creates a new appending a text search parameter specific to card comments. + + The current search + The text to search for. + A new parameter list. + + + + Creates a new appending a text search parameter specific to check lists. + + The current search + The text to search for. + A new parameter list. + + + + Creates a new appending a member search parameter. + + The current search + The member to search for. + A new parameter list. + + + + Creates a new appending a label search parameter. + + The current search + The label to search for. + A new parameter list. + + + + Creates a new appending a label color search parameter. + + The current search + The label color to search for. + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only archived items. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only unarchived items. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only starred items. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the next 24 hours. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the next week. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the next month. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the next hours. + + The current search + The number of days. + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items which are overdue. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the past 24 hours. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the past week. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the past month. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the past days. + + The current search + The number of days. + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the past 24 hours. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the past week. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the past month. + + The current search + A new parameter list. + + + + Creates a new appending a search parameter to restrict to only items due in the past days. + + The current search + The number of days. + A new parameter list. + + + + Defines the JSON structure for the BoardBackground object. + + + + + Gets or sets the color. + + + + + Gets or sets the url for the image. + + + + + Gets or sets a collection of scaled images. + + + + + Gets or sets whether the image should be tiled when displayed. + + + + + Defines the JSON structure for the Sticker object. + + + + + Gets or sets the position of the left edge of the sticker. + + + + + Gets or sets the name of the sticker. + + + + + Gets or sets a collection of previews for the attachment. + + + + + Gets or sets the rotation angle of the sticker in degrees. + + + + + Gets or sets the position of the top edge of the sticker. + + + + + Gets or sets the image's URL. + + + + + Gets or sets the sticker's z-index. + + + + + Declares that the JSON property should be deserialized. + + + + + Declares that the JSON property should be serialized and whether it + is optional or required. + + + + + Gets or sets whether this property is required by the Trello API. + + + + + Declares that the JSON property has a special serialization. + + + + + Defines the JSON structure for the ActionOldData object. + + + + + Gets or sets an old description. + + + + + Gets or sets an old list. + + + + + Gets or sets an old position. + + + + + Gets or sets old text. + + + + + Gets or sets whether an item was closed. + + + + + Defines the JSON structure for the ActionOldData object. + + + + + Gets or sets an old description. + + + + + Gets or sets an old list. + + + + + Gets or sets an old position. + + + + + Gets or sets old text. + + + + + Gets or sets whether an item was closed. + + + + + Defines the JSON structure for the Comment object. + + + + + Gets or sets the text content of the comment. + + + + + Defines the JSON structure for a member search. + + + + + Gets or sets a list of members. + + + + + Gets or sets a board within which the search should run. + + + + + Gets or sets the number of results to return. + + + + + Gets or sets whether only organization members should be returned. + + + + + Gets or sets an organization within which the search should run. + + + + + Gets or sets the search query. + + + + + Defines the JSON structure for the Position object. + + + + + Gets or sets an explicit numeric value for the position. + + + + + Gets or sets a named value for the position. + + + Valid values are "top" and "bottom". + + + + + Defines the JSON structure for a single-value parameter. + + + + + Gets or sets a string parameter value; + + + + + Gets or sets a boolean parameter value; + + + + + Defines properties required for TrelloService to cache an item. + + + + + Gets or sets a unique identifier (not necessarily a GUID). + + + + + Creates instances of JSON interfaces. + + + + + Creates an instance of the requested JSON interface. + + The type to create. + An instance of the requested type. + + + + Defines the JSON structure for the MemberSession object. + + + + + Gets or sets whether this session is active. + + + + + Gets or sets whether this session has been accessed recently. + + + + + Gets or sets the ID for this session. + + + + + Gets or sets the date this session was created. + + + + + Gets or sets the date this session expires. + + + + + Gets or sets the date this session was last used. + + + + + Gets or sets the IP address associated with this session. + + + + + Gets or sets the type of session. + + + + + Gets or sets the user agent associated with this session. + + + + + Defines the JSON structure for the OrganizationMembership object. + + + + + Gets or sets the ID of the member. + + + + + Gets or sets the membership type. + + + + + Gets or sets whether the membership is unconfirmed. + + + + + Defines the JSON structure for the Webhook object. + + + + + Gets or sets the description of the webhook. + + + + + Gets or sets the ID of the entity which the webhook monitors. + + + + + Gets or sets the URL which receives notification messages. + + + + + Gets or sets whether the webhook is active. + + + + + Defines the JSON structure for the WebhookNotification object. + + + + + Gets or sets the action associated with the notification. + + + + + Defines the JSON structure for the Action object. + + + + + Gets or sets the ID of the member who performed the action. + + + + + Gets or sets the data associated with the action. Contents depend upon the action's type. + + + + + Gets or sets the action's type. + + + + + Gets or sets the date on which the action was performed. + + + + + Defines the JSON structure for the ActionData object. + + + + + Gets or sets an attachment associated with the action if any. + + + + + Gets or sets a board associated with the action if any. + + + + + Gets or sets a board associated with the action if any. + + + + + Gets or sets a board associated with the action if any. + + + + + Gets or sets a card associated with the action if any. + + + + + Gets or sets a card associated with the action if any. + + + + + Gets or sets a check item associated with the action if any. + + + + + Gets or sets a check list associated with the action if any. + + + + + Gets or sets the last date/time that a comment was edited. + + + + + Gets or sets a list associated with the action if any. + + + + + Gets or sets a destination list associated with the action if any. + + + + + Gets or sets a source list associated with the action if any. + + + + + Gets or sets a member associated with the action if any. + + + + + Gets or sets an organization associated with the action if any. + + + + + Gets or sets any previous data associated with the action. + + + + + Gets or sets text associated with the action if any. + + + + + Gets or sets a custom value associate with the action if any. + + + + + Defines the JSON structure for the Attachment object. + + + + + Gets or sets the size of the attachment. + + + + + Gets or sets the date on which the attachment was created. + + + + + Gets or sets the ID of the member who created the attachment. + + + + + ? + + + + + Gets or sets the type of attachment. + + + + + Gets or sets the name of the attachment. + + + + + Gets or sets a collection of previews for the attachment. + + + + + Gets or sets the attachment storage location. + + + + + Defines the JSON structure for the AttachmentPreview object. + + + + + Gets or sets the width in pixels of the attachment preview. + + + + + Gets or sets the height in pixels of the attachment preview. + + + + + Gets or sets the attachment storage location. + + + + + Gets or sets whether the attachment was scaled to produce the preview. + + + + + Defines the JSON structure for the Badges object. + + + + + Gets or sets the number of votes. + + + + + Gets or sets whether the member has voted for this card. + + + + + Gets or sets whether the member is subscribed to the card. + + + + + Gets or sets the FogBugz ID. + + + + + Gets or sets the due date, if one exists. + + + + + Gets or sets whether the card has a description. + + + + + Gets or sets the number of comments. + + + + + Gets or sets the number of check items which have been checked. + + + + + Gets or sets the number of check items. + + + + + Gets or sets the number of attachments. + + + + + Defines the JSON structure for the Board object. + + + + + Gets or sets the board's name. + + + + + Gets or sets the board's description. + + + + + Gets or sets whether this board is closed. + + + + + Gets or sets the ID of the organization, if any, to which this board belongs. + + + + + Gets or sets a set of preferences for the board. + + + + + Gets or sets the URL for this board. + + + + + Gets or sets whether the user is subscribed to this board. + + + + + Gets or sets a board to be used as a template. + + + + + Defines the JSON structure for the BoardMembership object. + + + + + Gets or sets the ID of the member. + + + + + Gets or sets the membership type. + + + + + Gets or sets whether the membership is deactivated. + + + + + Defines the JSON structure for the BoardPersonalPreferences object. + + + + + Gets or sets whether the side bar (right side of the screen) is shown + + + + + Gets or sets whether the members section of the list of the side bar is shown. + + + + + Gets or sets whether the board actions (Add List/Add Member/Filter Cards) section of the side bar is shown. + + + + + Gets or sets whether the activity section of the side bar is shown. + + + + + Gets or sets whether the list guide (left side of the screen) is expanded. + + + + + Gets or sets the position of new cards when they are added via email. + + + + + Gets or sets the list for new cards when they are added via email. + + + + + Defines the JSON structure for the BoardPreferences object. + + + + + Gets or sets who may view the board. + + + + + Gets or sets who may vote on cards. + + + + + Gets or sets who may comment on cards. + + + + + Gets or sets who may extend invitations to join the board. + + + + + Gets or sets whether a Trello member may join the board without an invitation. + + + + + Gets or sets whether card covers are shown on the board. + + + + + Gets or sets whether the calendar feed is enabled. + + + + + Gets or sets the style of card aging is used, if the power up is enabled. + + + + + Gets or sets the background image of the board. + + + + + Defines the JSON structure for the BoardVisibilityRestrict object. + + + + + Gets or sets the visibility of publicly-visible boards owned by the organization. + + + + + Gets or sets the visibility of Org-visible boards owned by the organization. + + + + + Gets or sets the visibility of private boards owned by the organization. + + + + + Defines the JSON structure for the Card object. + + + + + Gets or set the badges displayed on the card cover. + + + + + Gets or sets whether a card has been archived. + + + + + Gets or sets the date of last activity for a card. + + + + + Gets or sets the card's description. + + + + + Gets or sets the card's due date. + + + + + Gets or sets the ID of the board which contains the card. + + + + + Gets or sets the ID of the list which contains the card. + + + + + Gets or sets the card's short ID. + + + + + Gets or sets the ID of the attachment cover image. + + + + + Gets or sets the labels assigned to this card. + + + + + Gets or sets whether the cover attachment was manually selected + + + + + Gets or sets the card's name + + + + + Gets or sets the card's position. + + + + + Gets or sets the URL for this card. + + + + + Gets or sets the short URL for this card. + + + + + Gets or sets whether the current member is subscribed to this card. + + + + + Gets or sets a card to be used as a template during creation. + + + + + Gets or sets a URL to be imported during creation. + + + + + Gets or sets whether the due date should be serialized, even if it is null. + + + + + Defines the JSON structure for the CheckItem object. + + + + + Gets or sets the check state of the checklist item. + + + + + Gets or sets the name of the checklist item. + + + + + Gets or sets the position of the checklist item. + + + + + Defines the JSON structure for the CheckList object. + + + + + Gets or sets the name of this checklist. + + + + + Gets or sets the ID of the board which contains this checklist. + + + + + Gets or sets the ID of the card which contains this checklist. + + + + + Gets or sets the collection of items in this checklist. + + + + + Gets or sets the position of this checklist. + + + + + Gets or sets a checklist to copy during creation. + + + + + Defines the JSON structure for the Label object. + + + + + Gets and sets the board on which the label is defined. + + + + + Gets and sets the color of the label. + + + + + Determines if the color property should be submitted even if it is null. + + + This property is not part of the JSON structure. + + + + + Gets and sets the name of the label. + + + + + Gets and sets how many cards use this label. + + + + + Defines the JSON structure for the List object. + + + + + Gets or sets the name of the list. + + + + + Gets or sets whether the list is archived. + + + + + Gets or sets the ID of the board which contains the list. + + + + + Gets or sets the position of the list. + + + + + Gets or sets whether the current member is subscribed to the list. + + + + + Defines the JSON structure for the Member object. + + + + + Gets or sets the member's avatar hash. + + + + + Gets or sets the bio of the member. + + + + + Gets the member's full name. + + + + + Gets or sets the member's initials. + + + + + Gets or sets the type of member. + + + + + Gets or sets the member's activity status. + + + + + Gets or sets the URL to the member's profile. + + + + + Gets or sets the member's username. + + + + + Gets or sets the source URL for the member's avatar. + + + + + Gets or sets whether the member is confirmed. + + + + + Gets or sets the member's registered email address. + + + + + Gets or sets the member's Gravatar hash. + + + + + Gets or sets the login types for the member. + + + + + Gets or sets the trophies obtained by the member. + + + + + Gets or sets the user's uploaded avatar hash. + + + + + Gets or sets the types of message which are dismissed for the member. + + + + + Gets or sets the similarity of the member to a search query. + + + + + Gets or sets a set of preferences for the member. + + + + + Defines the JSON structure for the MemberPreferences object. + + + + + Gets or sets the number of minutes between summary emails. + + + + + Enables/disables color-blind mode. + + + + + Defines the JSON structure for the Notification object. + + + + + Gets or sets whether the notification has been read. + + + + + Gets or sets the notification's type. + + + + + Gets or sets the date on which the notification was created. + + + + + Gets or sets the data associated with the notification. Contents depend upon the notification's type. + + + + + Gets or sets the ID of the member whose action spawned the notification. + + + + + Defines the JSON structure for the NotificationData object. + + + + + Gets or sets an attachment associated with the action if any. + + + + + Gets or sets a board associated with the action if any. + + + + + Gets or sets a board associated with the action if any. + + + + + Gets or sets a board associated with the action if any. + + + + + Gets or sets a card associated with the action if any. + + + + + Gets or sets a card associated with the action if any. + + + + + Gets or sets a check item associated with the action if any. + + + + + Gets or sets a check list associated with the action if any. + + + + + Gets or sets a list associated with the action if any. + + + + + Gets or sets a destination list associated with the action if any. + + + + + Gets or sets a source list associated with the action if any. + + + + + Gets or sets a member associated with the action if any. + + + + + Gets or sets an organization associated with the action if any. + + + + + Gets or sets any previous data associated with the action. + + + + + Gets or sets text associated with the action if any. + + + + + Defines the JSON structure for the Organization object. + + + + + Gets or sets the name of the organization. + + + + + Gets or sets the name to be displayed for the organization. + + + + + Gets or sets the description for the organization. + + + + + Gets or sets the URL to the organization's profile. + + + + + Gets or sets the organization's website. + + + + + Gets or sets the organization's logo hash. + + + + + Enumerates the powerups obtained by the organization. + + + + + Gets or sets whether the organization is a paid account. + + + + + Gets or sets a set of preferences for the organization. + + + + + Gets or sets a collection of premium features available to the organization. + + + + + Defines the JSON structure for the OrganizationPreferences object. + + + + + Gets or sets the permission level. + + + + + Gets or sets organization invitation restrictions. + + + + + Gets or sets whether external members are disabled. + + + + + Gets or sets the Google Apps domain. + + + + + Gets or sets the visibility of boards owned by the organization. + + + + + Defines methods required by the IRestClient to deserialize a response + from JSON to an object. + + + + + Attempts to deserialize a RESTful response to the indicated type. + + The type of object expected. + The response object which contains the JSON to deserialize. + The requested object, if JSON is valid; null otherwise. + + + + Attempts to deserialize a RESTful response to the indicated type. + + The type of object expected. + A string which contains the JSON to deserialize. + The requested object, if JSON is valid; null otherwise. + + + + Defines the JSON structure for the Search object. + + + + + Lists the IDs of actions which match the query. + + + + + Lists the IDs of boards which match the query. + + + + + Lists the IDs of cards which match the query. + + + + + Lists the IDs of members which match the query. + + + + + Lists the IDs of organizations which match the query. + + + + + Gets or sets the search query. + + + + + Gets or sets a collection of boards, cards, and organizations within + which the search should run. + + + + + Gets or sets which types of objects should be returned. + + + + + Gets or sets how many results to return; + + + + + Gets or sets whether the search should match on partial words. + + + + + Defines the JSON structure for the Token object. + + + + + Gets or sets the identifier of the application which requested the token. + + + + + Gets or sets the ID of the member who issued the token. + + + + + Gets or sets the date the token was created. + + + + + Gets or sets the date the token will expire, if any. + + + + + Gets or sets the collection of permissions granted by the token. + + + + + Defines the JSON structure for the TokenPermission object. + + + + + Gets or sets the ID of the model to which a token grants permissions. + + + + + Gets or sets the type of the model. + + + + + Gets or sets whether a token grants read permissions to the model. + + + + + Gets or sets whether a token grants write permissions to the model. + + + + + Defines methods required by the IRestClient to serialize an object to JSON. + + + + + Serializes an object to JSON. + + The object to serialize. + An equivalent JSON string. + + + + A read-only collection of scaled versions of board backgrounds. + + + + + Implement to provide data to the collection. + + + + + Provides an easy mechanism to build search queries. + + + + + Creates a new specifying a text search parameter. + + The text to search for. + + + + + Creates a new specifying a text search parameter specific to card names. + + The text to search for. + A new parameter list. + + + + Creates a new specifying a text search parameter specific to card descriptions. + + The text to search for. + A new parameter list. + + + + Creates a new specifying a text search parameter specific to card comments. + + The text to search for. + A new parameter list. + + + + Creates a new specifying a text search parameter specific to check lists. + + The text to search for. + A new parameter list. + + + + Creates a new specifying a member search parameter. + + The member to search for. + A new parameter list. + + + + Creates a new specifying a label search parameter. + + The label to search for. + A new parameter list. + + + + Creates a new specifying a label color search parameter. + + The label color to search for. + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only archived items. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only unarchived items. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only starred items. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the next 24 hours. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the next week. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the next month. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the next hours. + + The number of days. + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items which are overdue. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the past 24 hours. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the past week. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the past month. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the past days. + + The number of days. + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the past 24 hours. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the past week. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the past month. + + A new parameter list. + + + + Creates a new specifying a search parameter to restrict to only items due in the past days. + + The number of days. + A new parameter list. + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + A read-only collection of attachments. + + + + + Implement to provide data to the collection. + + + + + A collection of s. + + + + + Adds a to a . + + The name of the sticker. + The position of the left edge. + The position of the top edge. + The z-index. Default is 0. + The rotation. Default is 0. + The attachment generated by Trello. + Thrown when is null, empty, or whitespace. + Thrown when is less than 0 or greater than 359. + + + + A collection of s. + + + + + Adds a to a 's custom sticker set by uploading data. + + The byte data of the file to attach. + A name for the attachment. + The attachment generated by Trello. + + + + A read-only collection of image previews for attachments. + + + + + Implement to provide data to the collection. + + + + + Represents the user-specific preferences for a board. + + + + + Gets or sets the which will be used to post new cards + submitted by email. + + + + + Gets or sets the within a which + will be used to post new cards submitted by email. + + + + + Gets or sets whether to show the list guide. + + + It appears that this may be deprecated by Trello. + + + + + Gets or sets whether to show the side bar. + + + + + Gets or sets whether to show the activity list in the side bar. + + + It appears that this may be deprecated by Trello. + + + + + Gets or sets whether to show the board action list in the side bar. + + + It appears that this may be deprecated by Trello. + + + + + Gets or sets whether to show the board members in the side bar. + + + It appears that this may be deprecated by Trello. + + + + + Enumerates the various styles of aging for the Card Aging power up. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates that cards will age by fading. + + + + + Indicates that cards will age using a treasure map effect. + + + + + A read-only collection of actions. + + + + + Implement to provide data to the collection. + + + + + Exposes any data associated with an action. + + + + + Gets an assocated attachment. + + + + + Gets an assocated board. + + + + + Gets an assocated board. + + + + + Gets an assocated board. + + + + + Gets an assocated card. + + + + + Gets an assocated card. + + + + + Gets an assocated checklist item. + + + + + Gets an assocated checklist. + + + + + Gets the date/time a comment was last edited. + + + + + Gets an assocated list. + + + + + Gets the current list. + + + For some action types, this information may be in the + or properties. + + + + + Gets the previous list. + + + For some action types, this information may be in the + or properties. + + + + + Gets an assocated member. + + + + + Gets the previous description. + + + + + Gets the previous list. + + + For some action types, this information may be in the + or properties. + + + + + Gets the previous position. + + + + + Gets the previous text value. + + + + + Gets an assocated organization. + + + + + Gets assocated text. + + + + + Gets whether the object was previously archived. + + + + + Gets a custom value associate with the action if any. + + + + + Represents an attachment to a card. + + + + + Gets the size of the attachment in bytes. + + + + + Gets the creation date of the attachment. + + + + + Gets the date and time the attachment was added to a card. + + + + + Gets the attachment's ID. + + + + + Gets whether the attachment was uploaded data or attached by URI. + + + + + Gets the who added the attachment. + + + + + Gets the MIME type of the attachment. + + + + + Gets the name of the attachment. + + + + + Gets the collection of previews generated by Trello. + + + + + Gets the URI of the attachment. + + + + + Raised when data on the attachment is updated. + + + + + Deletes the attachment. + + + This cannot be undone. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + A read-only collection of attachments. + + + + + Implement to provide data to the collection. + + + + + A collection of attachments. + + + + + Adds an attachment to a card using the URL of the attachment. + + A URL to the data to attach. Must be a valid URL that begins with 'http://' or 'https://'. + An optional name for the attachment. The linked file name will be used by default if not specified. + The attachment generated by Trello. + + + + Adds an attachment to a card by uploading data. + + The byte data of the file to attach. + A name for the attachment. + The attachment generated by Trello. + + + + Represents a preview for an attachment on a card. + + + + + Gets the creation date of the image preview. + + + + + Gets the preview's height in pixels. + + + + + Gets the preview's ID. + + + + + Gets whether the attachment was scaled to generate the preview. + + + + + Gets the URI where the preview data is stored. + + + + + Gets the preview's width in pixels. + + + + + A read-only collection of actions. + + + + + Implement to provide data to the collection. + + + + + A collection of comment actions. + + + + + Posts a new comment to a card. + + The content of the comment. + The associated with the comment. + + + + Represents the preferences for a board. + + + + + Gets or sets the general visibility of the board. + + + + + Gets or sets whether voting is enabled and which members are allowed + to vote. + + + + + Gets or sets whether commenting is enabled and which members are + allowed to add comments. + + + + + Gets or sets which members may invite others to the board. + + + + + Gets or sets whether any Trello member may join the board themselves + or if an invitation must be sent. + + + + + Gets or sets whether card covers are shown. + + + + + Gets or sets whether the calendar feed is enabled. + + + + + Gets or sets the card aging style for the Card Aging power up. + + + + + Gets or sets the background of the board. + + + + + Defines properties which are required to cache an object. + + + + + Gets an ID on which matching can be performed. + + + + + Definines properties and methods required to support webhooks. + + + + + Applies the changes an action represents. + + The action. + + + + Defines properties which are required to perform a search within an object. + + + + + Defines operations for a cache. + + + + + Adds an object to the cache, if it does not already exist. + + The object to add. + + + + Finds an object of a certain type meeting specified criteria. + + The type of object to find. + A function which evaluates the matching criteria. + + + + Removes an object from the cache, if it exists. + + The object to remove. + + + + Removes all objects from the cache. + + + + + Defines methods required to log information, events, and errors generated + throughout Manatee.Trello. + + + + + Writes a debug level log entry. + + The message or message format. + A list of parameters. + + + + Writes an information level log entry. + + The message or message format. + A list of paramaters. + + + + Writes an error level log entry. + + The exception that will be or was thrown. + true if the exception should be thrown; false otherwise. + + Manatee.Trello relies on the logger to throw any exceptions. Not implmenting this functionality + may result in undesired behavior. + + + + + Enumerates known visibility levels for board in orgainzations. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates that a board within an organization will not be visible. + + + + + Indicates that a board within an organization will be visible to the organization admins. + + + + + Indicates that a board within an organization will be visible to the organization members. + + + + + Thrown when validation fails on a Trello object property. + + + + + Gets a collection of errors that occurred while validating the value. + + + + + Creates a new instance of the object. + + + + + + + Thrown when Trello reports an error with a request. + + + + + Creates a new instance of the TrelloInteractionException class. + + The exception which occurred during the call. + + + + Extension methods for boards. + + + + + Gets all open cards for a member on a specific board. + + The board. + The member. + A containing the member's cards. + + + + Extension methods for ICache implementations. + + + + + Gets an already-instantiated object or creates one. + + The type of object to get. + The ICache implementation. + The ID of the object. + (Optional) Custom authorization parameters. When not provided, + will be used. + The object requested. + + + + Extension methods for members. + + + + + Gets all open cards for a member. + + The member. + A containing the member's cards. + + + + Represents preferences for a member. + + + + + Gets or sets whether color-blind mode is enabled. + + + + + Gets or sets the time between email summaries. + + + + + Performs a search for members. + + + + + Gets the collection of results returned by the search. + + + + + Creates a new instance of the object and performs the search. + + The query. + Optional - The result limit. Can be a value from 1 to 20. The default is 8. + Optional - A board to which the search should be limited. + Optional - An organization to which the search should be limited. + Optional - Restricts the search to only organization members. + (Optional) Custom authorization parameters. When not provided, + will be used. + + + + Marks the member search to be refreshed the next time data is accessed. + + + + + Represents a result from a member search. + + + + + Gets the returned member. + + + + + Gets a value indicating the similarity of the member to the search query. + + + + + Represents a notification. + + + + + Gets the creation date of the notification. + + + + + Gets the member who performed the action which created the notification. + + + + + Gets any data associated with the notification. + + + + + Gets the date and teim at which the notification was issued. + + + + + Gets the notification's ID. + + + + + Gets or sets whether the notification has been read. + + + + + Gets the type of notification. + + + + + Raised when data on the notification is updated. + + + + + Creates a new object. + + The notification's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Exposes any data associated with a notification. + + + + + Gets an assocated attachment. + + + + + Gets an assocated board. + + + + + Gets an assocated board. + + + + + Gets an assocated board. + + + + + Gets an assocated card. + + + + + Gets an assocated card. + + + + + Gets an assocated checklist item. + + + + + Gets an assocated checklist. + + + + + Gets an assocated list. + + + + + Gets the current list. + + + For some action types, this information may be in the + or properties. + + + + + Gets the previous list. + + + For some action types, this information may be in the + or properties. + + + + + Gets an assocated member. + + + + + Gets the previous description. + + + + + Gets the previous list. + + + For some action types, this information may be in the + or properties. + + + + + Gets the previous position. + + + + + Gets the previous text value. + + + + + Gets an assocated organization. + + + + + Gets assocated text. + + + + + Gets whether the object was previously archived. + + + + + A read-only collection of organization memberships. + + + + + Retrieves a membership which matches the supplied key. + + The key to match. + The matching list, or null if none found. + + Matches on OrganizationMembership.Id, OrganizationMembership.Member.Id, + OrganizationMembership.Member.FullName, and OrganizationMembership.Member.Username. + Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of organization memberships. + + + + + Adds a member to an organization with specified privileges. + + The member to add. + The membership type. + + + + Removes a member from an organization. + + The member to remove. + + + + Represents the permission level a member has on an organization. + + + + + Gets the creation date of the membership. + + + + + Gets the membership definition's ID. + + + + + Gets whether the member has accepted the invitation to join Trello. + + + + + Gets the member. + + + + + Gets the membership's permission level. + + + + + Raised when data on the membership is updated. + + + + + Marks the organization membership to be refreshed the next time data is accessed. + + + + + Represents the permission level a member has on a board. + + + + + Gets the creation date of the membership. + + + + + Gets the membership definition's ID. + + + + + Gets whether the member has accepted the invitation to join Trello. + + + + + Gets the member. + + + + + Gets the membership's permission level. + + + + + Raised when data on the membership is updated. + + + + + Marks the board membership to be refreshed the next time data is accessed. + + + + + A read-only collection of board memberships. + + + + + Retrieves a membership which matches the supplied key. + + The key to match. + The matching membership, or null if none found. + + Matches on BoardMembership.Id, BoardMembership.Member.Id, + BoardMembership.Member.Name, and BoardMembership.Usernamee. + Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of board memberships. + + + + + Adds a member to a board with specified privileges. + + The member to add. + The membership type. + + + + Removes a member from a board. + + The member to remove. + + + + Represents a checklist item. + + + + + Gets or sets the checklist to which the item belongs. + + + + + Gets the creation date of the checklist item. + + + + + Gets or sets the checklist item's ID. + + + + + Gets or sets the checklist item's name. + + + + + Gets or sets the checklist item's position. + + + + + Gets or sets the checklist item's state. + + + + + Raised when data on the checklist item is updated. + + + + + Deletes the checklist item. + + + This permanently deletes the checklist item from Trello's server, however, this + object will remain in memory and all properties will remain accessible. + + + + + Marks the checklist item to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + A read-only collection of checklist items. + + + + + Retrieves a check list item which matches the supplied key. + + The key to match. + The matching check list item, or null if none found. + + Matches on CheckItem.Id and CheckItem.Name. Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of checklist items. + + + + + Creates a new checklist item. + + The name of the checklist item to add. + The generated by Trello. + + + + A read-only collection of checklists. + + + + + Retrieves a check list which matches the supplied key. + + The key to match. + The matching check list, or null if none found. + + Matches on CheckList.Id and CheckList.Name. Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of checklists. + + + + + Creates a new checklist, optionally by copying a checklist. + + The name of the checklist to add. + A checklist to use as a template. + The generated by Trello. + + + + A read-only collectin of boards. + + + + + Retrieves a board which matches the supplied key. + + The key to match. + The matching board, or null if none found. + + Matches on Board.Id and Board.Name. Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of boards. + + + + + Creates a new board. + + The name of the board to create. + A board to use as a template. + The generated by Trello. + + + + Represents a checklist. + + + + + Gets the board on which the checklist belongs. + + + + + Gets or sets the card on which the checklist belongs. + + + + + Gets the collection of items in the checklist. + + + + + Gets the creation date of the checklist. + + + + + Gets the checklist's ID. + + + + + Gets the checklist's name. + + + + + Gets the checklist's position. + + + + + Retrieves a check list item which matches the supplied key. + + The key to match. + The matching check list item, or null if none found. + + Matches on CheckItem.Id and CheckItem.Name. Comparison is case-sensitive. + + + + + Retrieves the check list item at the specified index. + + The index. + The check list item. + + is less than 0 or greater than or equal to the number of elements in the collection. + + + + + Raised when data on the check list is updated. + + + + + Creates a new instance of the object. + + The check list's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + + + Deletes the checklist. + + + This permanently deletes the checklist from Trello's server, however, this object + will remain in memory and all properties will remain accessible. + + + + + Marks the checklist to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Represents a collection of badges which summarize the contents of a card. + + + + + Gets the number of attachments on this card. + + + + + Gets the number of check items on this card. + + + + + Gets the number of check items on this card which are checked. + + + + + Gets the number of comments on this card. + + + + + Gets the due date for this card. + + + + + Gets some FogBugz information. + + + + + Gets whether this card has a description. + + + + + Gets whether the current member has voted for this card. + + + + + Gets whether the current member is subscribed to this card. + + + + + Gets the number of votes for this card. + + + + + Represents a board. + + + + + Gets the collection of actions performed on and within this board. + + + + + Gets the collection of cards contained within this board. + + + This property only exposes unarchived cards. + + + + + Gets the creation date of the board. + + + + + Gets or sets the board's description. + + + + + Gets the board's ID. + + + + + Gets or sets whether this board is closed. + + + + + Gets or sets whether the current member is subscribed to this board. + + + + + Gets the collection of labels for this board. + + + + + Gets the collection of lists on this board. + + + This property only exposes unarchived lists. + + + + + Gets the collection of members on this board. + + + + + Gets the collection of members and their priveledges on this board. + + + + + Gets or sets the board's name. + + + + + Gets or sets the organization to which this board belongs. + + + Setting null makes the board's first admin the owner. + + + + + Gets the set of preferences for the board. + + + + + Gets the set of preferences for the board. + + + + + Gets the board's URI. + + + + + Retrieves a list which matches the supplied key. + + The key to match. + The matching list, or null if none found. + + Matches on List.Id and List.Name. Comparison is case-sensitive. + + + + + Retrieves the list at the specified index. + + The index. + The list. + + is less than 0 or greater than or equal to the number of elements in the collection. + + + + + Raised when data on the board is updated. + + + + + Creates a new instance of the object. + + The board's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + + + Applies the changes an action represents. + + The action. + + + + Marks the board to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Represents a card. + + + + + Gets the collection of actions performed on this card. + + By default imposed by Trello, this contains actions of types and . + + + + Gets the collection of attachments contained in the card. + + + + + Gets the badges summarizing the content of the card. + + + + + Gets the board to which the card belongs. + + + + + Gets the collection of checklists contained in the card. + + + + + Gets the collection of comments made on the card. + + + + + Gets the creation date of the card. + + + + + Gets or sets the card's description. + + + + + Gets or sets the card's due date. + + + + + Gets the card's ID. + + + + + Gets or sets whether the card is archived. + + + + + Gets or sets whether the current member is subscribed to the card. + + + + + Gets the collection of labels on the card. + + + + + Gets the most recent date of activity on the card. + + + + + Gets or sets the list to the card belongs. + + + + + Gets the collection of members who are assigned to the card. + + + + + Gets or sets the card's name. + + + + + Gets or sets the card's position. + + + + + Gets the card's short ID. + + + + + Gets the card's short URL. + + + Because this value does not change, it can be used as a permalink. + + + + + Gets the collection of stickers which appear on the card. + + + + + Gets the card's full URL. + + + Trello will likely change this value as the name changes. You can use for permalinks. + + + + + Retrieves a check list which matches the supplied key. + + The key to match. + The matching check list, or null if none found. + + Matches on CheckList.Id and CheckList.Name. Comparison is case-sensitive. + + + + + Retrieves the check list at the specified index. + + The index. + The check list. + + is less than 0 or greater than or equal to the number of elements in the collection. + + + + + Raised when data on the card is updated. + + + + + Creates a new instance of the object. + + The card's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + The supplied ID can be either the full or short ID. + + + + + Applies the changes an action represents. + + The action. + + + + Deletes the card. + + + This permanently deletes the card from Trello's server, however, this object will + remain in memory and all properties will remain accessible. + + + + + Marks the card to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + A read-only collection of cards. + + + + + Retrieves a card which matches the supplied key. + + The key to match. + The matching card, or null if none found. + + Matches on Card.Id and Card.Name. Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of cards. + + + + + Creates a new card. + + The name of the card to add. + The generated by Trello. + + + + Creates a new card by copying a card. + + A card to copy. Default is null. + The generated by Trello. + + + + Creates a new card by importing data from a URL. + + The name of the card to add. + + + + + + Enumerates the avatar sources used by Trello. + + + + + Indicates the avatar source is not recognized. + + + + + Indicates there is no avatar. + + + + + Indicates the avatar has been uploaded by the user. + + + + + Indicates the avatar is supplied by Gravatar. + + + + + Enumerates the accepted values for the MinutesBetweenSummaries property on the + MemberPreferences object. + + + + + Indicates that summary emails are disabled. + + + + + Indicates that summary emails should be sent every minute, when notifications + are present. + + + + + Indicates that summary emails should be sent every hour, when notifications + are present. + + + + + Enumerates the model types for which one can search. + + + + + Indicates the search should return actions. + + + + + Indicates the search should return boards. + + + + + Indicates the search should return cards. + + + + + Indicates the search should return members. + + + + + Indicates the search should return organizations. + + + + + Indicates the search should return all model types. + + + + + Enumerates the model types to which a user token may grant access. + + + + + Assigned when the model type is not recognized. + + + + + Indicates the model is one or more Members. + + + + + Indicates the model is one or more Boards. + + + + + Indicates the model is one or more Organizations. + + + + + Enumerates known board membership types. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates the member is an admin of the organization. + + + + + Indicates the member is a normal member of the organization. + + + + + Indicates the member is may only view the organization. + + + + + Indicates the member has been invited, but has not yet joined Trello. + + + + + A label. + + + + + Gets the on which the label is defined. + + + + + Gets and sets the color. Use null for no color. + + + + + Gets the creation date of the label. + + + + + Gets the label's ID. + + + + + Gets and sets the label's name. + + + + + Gets the number of cards which use this label. + + + + + Deletes the label. All usages of the label will also be removed. + + + This permanently deletes the label from Trello's server, however, this object will + remain in memory and all properties will remain accessible. + + + + + Marks the label to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + A collection of labels for cards. + + + + + Adds a label to the collection. + + The label to add. + + + + Removes a label from the collection. + + The label to add. + + + + Implement to provide data to the collection. + + + + + A collection of labels for boards. + + + + + Adds a label to the collection. + + The name of the label. + The color of the label to add. + The generated by Trello. + + + + Implement to provide data to the collection. + + + + + Represents a list. + + + + + Gets the collection of actions performed on the list. + + + + + Gets or sets the board on which the list belongs. + + + + + Gets the collection of cards contained in the list. + + + + + Gets the creation date of the list. + + + + + Gets the list's ID. + + + + + Gets or sets whether the list is archived. + + + + + Gets or sets whether the current member is subscribed to the list. + + + + + Gets the list's name. + + + + + Gets the list's position. + + + + + Retrieves a card which matches the supplied key. + + The key to match. + The matching card, or null if none found. + + Matches on Card.Id and Card.Name. Comparison is case-sensitive. + + + + + Retrieves the card at the specified index. + + The index. + The card. + + is less than 0 or greater than or equal to the number of elements in the collection. + + + + + Raised when data on the list is updated. + + + + + Creates a new instance of the object. + + The list's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + + + Applies the changes an action represents. + + The action. + + + + Marks the list to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + A read-only collection of lists. + + + + + Retrieves a list which matches the supplied key. + + The key to match. + The matching list, or null if none found. + + Matches on List.Id and List.Name. Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of lists. + + + + + Creates a new list. + + The name of the list to add. + The generated by Trello. + + + + Represents the current member. + + + + + Gets or sets the source type for the member's avatar. + + + + + Gets or sets the member's bio. + + + + + Gets the collection of boards owned by the member. + + + + + Gets or sets the member's email. + + + + + Gets or sets the member's full name. + + + + + Gets or sets the member's initials. + + + + + Gets the collection of notificaitons for the member. + + + + + Gets the collection of organizations to which the member belongs. + + + + + Gets the set of preferences for the member. + + + + + Gets or sets the member's username. + + + + + Represents a member. + + + + + Returns the associated with the current User Token. + + + + + Gets the collection of actions performed by the member. + + + + + Gets the source type for the member's avatar. + + + + + Gets the URL to the member's avatar. + + + + + Gets the member's bio. + + + + + Gets the collection of boards owned by the member. + + + + + Gets the creation date of the member. + + + + + Gets the member's full name. + + + + + Gets the member's ID. + + + + + Gets or sets the member's initials. + + + + + Gets whether the member has actually join or has merely been invited (ghost). + + + + + Gets a string which can be used in comments or descriptions to mention another + user. The user will receive notification that they've been mentioned. + + + + + Gets the collection of organizations to which the member belongs. + + + + + Gets the member's online status. + + + + + Gets the collection of trophies earned by the member. + + + + + Gets the member's URL. + + + + + Gets the member's username. + + + + + Raised when data on the member is updated. + + + + + Creates a new instance of the object. + + The member's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + The supplied ID can be either the full ID or the username. + + + + + Applies the changes an action represents. + + The action. + + + + Marks the member to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + A read-only collection of members. + + + + + Retrieves a member which matches the supplied key. + + The key to match. + The matching member, or null if none found. + + Matches on Member.Id, Member.FullName, and Member.Username. Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of members. + + + + + Adds a member to the collection. + + The member to add. + + + + Removes a member from the collection. + + The member to remove. + + + + Represents an organization. + + + + + Gets the collection of actions performed on the organization. + + + + + Gets the collection of boards owned by the organization. + + + + + Gets the creation date of the organization. + + + + + Gets or sets the organization's description. + + + + + Gets or sets the organization's display name. + + + + + Gets the organization's ID. + + + + + Gets whether the organization has business class status. + + + + + Gets the collection of members who belong to the organization. + + + + + Gets the collection of members and their priveledges on this organization. + + + + + Gets the organization's name. + + + + + Gets the set of preferences for the organization. + + + + + Gets the organization's URL. + + + + + Gets or sets the organization's website. + + + + + Raised when data on the organization is updated. + + + + + Creates a new instance of the object. + + The organization's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + The supplied ID can be either the full ID or the organization's name. + + + + + Applies the changes an action represents. + + The action. + + + + Deletes the organization. + + + This permanently deletes the organization from Trello's server, however, this + object will remain in memory and all properties will remain accessible. + + + + + Marks the organization to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + A read-only collection of organizations. + + + + + Retrieves a organization which matches the supplied key. + + The key to match. + The matching organization, or null if none found. + + Matches on Organization.Id, Organization.Name, and Organization.DisplayName. Comparison is case-sensitive. + + + + + Implement to provide data to the collection. + + + + + A collection of organizations. + + + + + Creates a new organization. + + The display name of the organization to add. + The generated by Trello. + The organization's name will be derived from the display name and can be changed later. + + + + Represents the preferences for an organization. + + + + + Gets or sets the general visibility of the organization. + + + + + Gets or sets whether external members are disabled. + + + Still researching what this means. + + + + + Gets or sets a domain to associate with the organization. + + + Still researching what this means. + + + + + Gets or sets the visibility of public-viewable boards owned by the organizations. + + + + + Gets or sets the visibility of organization-viewable boards owned by the organization. + + + + + Gets or sets the visibility of private-viewable boards owned by the organization. + + + + + Represents the position of a checklist in a card, a card in a list, + or list in a board + + + + + Represents the top position. + + + + + Represents the bottom position. + + + + + Represents an invalid position. + + + + + Gets whether the position is valid. + + + + + Gets the internal numeric position value. + + + + + Creates a new instance of the class. + + A positive integer. + + + + Creates a new object between two others. + + A . + Another . + The new . + + + + Compares the current object with another object of the same type. + + + A value that indicates the relative order of the objects being compared. The return + value has the following meanings: + Value Meaning Less than zero This object is less than the parameter. + Zero This object is equal to . + Greater than zero This object is greater than . + + An object to compare with this object. + + + + Compares the current instance with another object of the same type and returns an integer that + indicates whether the current instance precedes, follows, or occurs in the same position in the + sort order as the other object. + + + A value that indicates the relative order of the objects being compared. The return + value has these meanings: + Value Meaning Less than zero This instance precedes in the sort order. + Zero This instance occurs in the same position in the sort order as . + Greater than zero This instance follows in the sort order. + + An object to compare with this instance. + + is not the same type as this instance. + + 2 + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Implicitly casts a PositionValue to a . + + The PositionValue value. + The Position object. + + + + Implicitly casts an int to a . + + a positive integer. + The Position object. + + + + Explicitly casts a to a double. + + The Position object. + The PositionValue value. + + + + Explicitly casts a to an int. + + The Position object. + The int value. + + + + Compares two objects by examining their content. + + A object. + A object. + True if equivalent, false otherwise. + + + + Compares two objects by examining their content. + + A object. + A object. + False if equivalent, true otherwise. + + + + Compares two values for linear order. + + A object. + A object. + True if the first operand is less than the second, false otherwise. + + + + Compares two values for linear order. + + A object. + A object. + True if the first operand is greater than the second, false otherwise. + + + + Compares two values for linear order. + + A object. + A object. + True if the first operand is less than or equal to the second, false otherwise. + + + + Compares two values for linear order. + + A object. + A object. + True if the first operand is greater than or equal to the second, false otherwise. + + + + Compares two object by examining their content. + + A object. + True if equivalent, false otherwise. + + + + Determines whether the specified is equal to the current . + + + true if the specified is equal to the current ; otherwise, false. + + The object to compare with the current object. 2 + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + 2 + + + + A read-only collection of image previews for attachments. + + + + + Implement to provide data to the collection. + + + + + Provides base functionality for a read-only collection. + + The type of object contained by the collection. + + + + Retrieves the item at the specified index. + + The index. + The item. + + is less than 0 or greater than or equal to the number of elements in the collection. + + + + + Creates a new instance of the object. + + + + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + 1 + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + 2 + + + + Implement to provide data to the collection. + + + + + Adds to a list of additional parameters. + + The list of additional parameters. + + + + Defines a file to be included in a REST request. + + + + + Defines a key to be used when attaching a file to a REST request. + + + + + The file name to use for the uploaded file + + + + + The file data + + + + + Defines methods required to make RESTful calls. + + + + + Makes a RESTful call and ignores any return data. + + The request. + + + + Makes a RESTful call and expects a single object to be returned. + + The expected type of object to receive in response. + The request. + The response. + + + + Defines methods required to create an instance of IRestClient. + + + + + Creates requests for the client. + + + + + Creates an instance of IRestClient. + + The base URL to be used by the client + An instance of IRestClient. + + + + Defines properties and methods required to make RESTful requests. + + + + + Gets and sets the method to be used in the call. + + + + + Gets the URI enpoint for the request. + + + + + Stores the response returned by the server. + + + + + Explicitly adds a parameter to the request. + + The name. + The value. + + + + Adds a body to the request. + + The body. + + + + Defines methods to generate IRequest objects used to make RESTful calls. + + + + + Creates a general request using a collection of objects and an additional parameter to + generate the resource string and an object to supply additional parameters. + + The method endpoint the request calls. + A list of paramaters to include in the request. + An IRestRequest instance which can be sent to an IRestClient. + + + + Defines properties required for objects returned by RESTful calls. + + + + + The JSON content returned by the call. + + + + + Gets any exception that was thrown during the call. + + + + + Defines required properties returned by RESTful calls. + + The type expected to be returned by the call. + + + + The deserialized data. + + + + + Enumerates the RESTful call methods required by TrelloService. + + + + + Indicates an HTTP GET operation. + + + + + Indicates an HTTP PUT operation. + + + + + Indicates an HTTP POST operation. + + + + + Indicates an HTTP DELETE operation. + + + + + Enumerates known types of s. + + + + + Not recognized. May have been created since the current version of this API. + + This value is not supported by Trello's API. + + + + Indicates an was added to a . + + + + + Indicates a was added to a . + + + + + Indicates a was added to a . + + + + + Indicates a was added to a . + + + + + Indicates a was added to an . + + + + + Indicates a was added to a . + + + + + Indicates a comment was added to a . + + + + + Indicates a item was converted to . + + + + + Indicates a was copied. + + + + + Indicates a was copied. + + + + + Indicates a comment was copied from one to another. + + + + + Indicates a was created. + + + + + Indicates a was created. + + + + + Indicates a was created. + + + + + Indicates an was created. + + + + + Indicates an was deleted from a . + + + + + Indicates an invitation to a was rescinded. + + + + + Indicates a was deleted. + + + + + Indicates an invitation to an was rescinded. + + + + + Indicates a power-up was disabled. + + + + + Indicates a was created via email. + + + + + Indicates a power-up was enabled. + + + + + Indicates a was made an admin of a . + + + + + Indicates a was made a normal of a . + + + + + Indicates a was made a normal of an . + + + + + Indicates a was made an observer of a . + + + + + Indicates a joined Trello. + + + + + Indicates a was moved from one to another. + + + + + Indicates a was moved from one to another. + + + + + Indicates a was moved from one to another. + + + + + Indicates a was moved from one to another. + + + + + Indicates a was removed from a . + + + + + Indicates an was removed from a . + + + + + Indicates a was removed from a . + + + + + Indicates an invitation to a was created. + + + + + Indicates an invitation to an was created. + + + + + Indicates a was updated. + + + + + Indicates a was updated. + + + + + Indicates a was archived or unarchived. + + + + + Indicates a description was updated. + + + + + Indicates a was moved to a new . + + + + + Indicates a name was updated. + + + + + Indicates a was checked or unchecked. + + + + + Indicates a was updated. + + + + + Indicates a updated a . + + + + + Indicates a archived a . + + + + + Indicates a updated the name of a . + + + + + Indicates a was updated. + + + + + Indicates an was updated. + + + + + Indictes the default set of values returned by . + + + + + Indicates all action types + + + + + Enumerates known board commenting permission levels. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates that only members of the board may comment on cards. + + + + + Indicates that observers may make comments on cards. + + + + + Indicates that only members of the organization to which the board belongs may comment on cards. + + + + + Indicates that any Trello member may comment on cards. + + + + + Indicates that no members may comment on cards. + + + + + Enumerates known board invitation permission levels. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates that any member of the board may extend an invitation to join the board. + + + + + Indicates that only admins of the board may extend an invitation to joni the board. + + + + + Enumerates known board membership types. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates the member is an admin of the board. + + + + + Indicates the member is a normal member of the board. + + + + + Indicates the member is may only view the board. + + + + + Indicates the member has been invited, but has not yet joined Trello. + + + + + Enumerates known values for board permission levels + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates that the board can only be viewed by its members. + + + + + Indicates that the board may be viewed by any member of the organization to which the board belongs. + + + + + Indicates that anyone (even non-Trello users) may view the board. + + + + + Enumerates known voting permission levels for a board + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates that only members of the board may vote on cards. + + + + + Indicates that only members of the organization to which the board belongs may vote on cards. + + + + + Indicates that any Trello member may vote on cards. + + + + + Indicates that no members may vote on cards. + + + + + Enumerates known values for an item in a checklist. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates that the checlist item is not checked. + + + + + Indicates that the checlist item is checked. + + + + + Enumerates label colors for a board. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates a green label. + + + + + Indicates a yellow label. + + + + + Indicates an orange label. + + + + + Indicates a red label. + + + + + Indicates a purple label. + + + + + Indicates a blue label. + + + + + Indicates a blue label. + + + + + Indicates a blue label. + + + + + Indicates a blue label. + + + + + Indicates a blue label. + + + + + Enumerates known values for a member's activity status. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates the member is not connected to the website. + + + + + Indicates the member is connected to the website but inactive. + + + + + Indicates the member is actively using the website. + + + + + Enumerates known types of notifications. + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates an attachment was added to a card. + + + + + Indicates the current member was added to a board. + + + + + Indicates the current member was added to a card. + + + + + Indicates the current member was added to an organization. + + + + + Indicates another member was added to an card. + + + + + Indicates the current member was added to a board as an admin. + + + + + Indicates the current member was added to an organization as an admin. + + + + + Indicates a card was changed. + + + + + Indicates a board was closed. + + + + + Indicates another member commented on a card. + + + + + Indicates another member created a card. + + + + + Indicates the current member was removed from a board. + + + + + Indicates the current member was removed from a card. + + + + + Indicates another member was removed from a card. + + + + + Indicates the current member was removed from an organization. + + + + + Indicates the current member was mentioned on a card. + + + + + Indicates a checklist item was updated. + + + + + Indicates the current member was made an admin of a board. + + + + + Indicates the current member was made an admin of an organization. + + + + + Indicates a card due date is approaching. + + + + + Indicates all notification types. + + + + + Performs a search. + + + + + Gets the collection of actions returned by the search. + + + + + Gets the collection of boards returned by the search. + + + + + Gets the collection of cards returned by the search. + + + + + Gets the collection of members returned by the search. + + + + + Gets the collection of organizations returned by the search. + + + + + Gets the query. + + + + + Creates a new instance of the object and performs the search. + + The query. + The maximum number of results to return. + (Optional) The desired model types to return. Can be joined using the | operator. Default is All. + (Optional) A collection of queryable items to serve as a context in which to search. + (Optional) Custom authorization parameters. When not provided, + will be used. + (Optional) Indicates whether to include matches that start with the query text. Default is false. + + + + Creates a new instance of the object and performs the search. + + The query. + The maximum number of results to return. + (Optional) The desired model types to return. Can be joined using the | operator. Default is All. + (Optional) A collection of queryable items to serve as a context in which to search. + (Optional) Custom authorization parameters. When not provided, + will be used. + (Optional) Indicates whether to include matches that start with the query text. Default is false. + + + + Marks the search to be refreshed the next time data is accessed. + + + + + Represents a sticker on a card. + + + + + Represents the stock Check sticker. + + + + + Represents the stock Heart sticker. + + + + + Represents the stock Warning sticker. + + + + + Represents the stock Clock sticker. + + + + + Represents the stock Smile sticker. + + + + + Represents the stock Laugh sticker. + + + + + Represents the stock Huh sticker. + + + + + Represents the stock Frown sticker. + + + + + Represents the stock ThumbsUp sticker. + + + + + Represents the stock ThumbsDown sticker. + + + + + Represents the stock Star sticker. + + + + + Represents the stock RocketShip sticker. + + + + + Gets the checklist's ID. + + + + + Gets or sets the position of the left edge. + + + + + Gets the name of the sticker. + + + + + Gets the collection of previews. + + + + + Gets or sets the rotation. + + + Rotation is clockwise and in degrees. + + + + + Gets or sets the position of the top edge. + + + + + Gets the URL for the sticker's image. + + + + + Gets or sets the z-index. + + + + + Raised when data on the attachment is updated. + + + + + Deletes the card. + + + This permanently deletes the card from Trello's server, however, this object will + remain in memory and all properties will remain accessible. + + + + + Marks the card to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Represents a user token. + + + + + Gets the name of the application associated with the token. + + + + + Gets the permissions on boards granted by the token. + + + + + Gets the creation date of the token. + + + + + Gets the date and time the token was created. + + + + + Gets the date and time the token expires, if any. + + + + + Gets the token's ID. + + + + + Gets the member for which the token was issued. + + + + + Gets the permissions on members granted by the token. + + + + + Gets the permissions on organizations granted by the token. + + + + + Creates a new instance of the object. + + The token's ID. + (Optional) Custom authorization parameters. When not provided, + will be used. + + The supplied ID can be either the full ID or the token itself. + + + + + Deletes the token. + + + This permanently deletes the token from Trello's server, however, this object will + remain in memory and all properties will remain accessible. + + + + + Marks the token to be refreshed the next time data is accessed. + + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Represents permissions granted by a token. + + + + + Gets whether a token can read values. + + + + + Gets whether a token can write values. + + + + + Contains authorization tokens needed to connect to trello.com. + + + + + Gets the default authorization. + + + + + The token which identifies the application attempting to connect. + + + + + The token which identifies special permissions as granted by a specific user. + + + + + Exposes a set of run-time options for Manatee.Trello. + + + + + Specifies the serializer for the REST client. + + + + + Specifies the deserializer for the REST client. + + + + + Specifies the REST client provider. + + + + + Provides a cache to manage all Trello objects. + + + + + Provides logging for Manatee.Trello. The default log writes to the Console window. + + + + + Provides a factory which is used to create instances of JSON objects. + + + + + Specifies whether the service should throw an exception when an error is received from Trello. Default is true. + + + + + Specifies a length of time after which each Trello object will be marked as expired. Default is 30 seconds. + + + + + Specifies a length of time an object holds changes before it submits them. The timer is reset with every change. Default is 100 ms. + + + Setting a value of 0 ms will result in instant upload of changes, dramatically increasing call volume and slowing performance. + + + + + Enumerates known values for organization permission levels + + + + + Not recognized. May have been created since the current version of this API. + + + + + Indicates that the organization can only be viewed by its members. + + + + + Indicates that anyone (even non-Trello users) may view the organization. + + + + + Provides options and control for the internal request queue processor. + + + + + Specifies whether the request processor can keep the application process alive after the main thread exits. Default is false. + + + When this property is set to true, the application must call at the end of execution. This can be used + to ensure that all requests are sent to Trello before an application ends. This property appears to have no effect in testing + environments. + + + + + Specifies the number of concurrent calls to the Trello API that the processor can make. Default is 1. + + + + + Signals the processor that the application is shutting down. The processor will reject any new changes and perform a + "last call" for pending requests. + + + + + Signals the processor that the application is shutting down. The processor will reject any new changes and perform a + "last call" for pending requests. + + + + + Defines a color in the RGB space. + + + + + Gets the red component. + + + + + Gets the green component. + + + + + Gets the blue component. + + + + + Creates a new instance of the class. + + The red component. + The green component. + The blue component. + + + + Creates a new isntance of the class. + + A string representation of RGB values in the format "#RRGGBB". + + + + Returns a string that represents the current object. + + + A string that represents the current object. + + 2 + + + + Provides a common base class for the generic Webhook classes. + + + + + Processes webhook notification content. + + The string content of the notification. + The under which the notification should be processed + + + + Represents a webhook. + + The type of object to which the webhook is attached. + + + + Gets or sets a callback URL for the webhook. + + + + + Gets the creation date of the webhook. + + + + + Gets or sets a description for the webhook. + + + + + Gets the webhook's ID> + + + + + Gets or sets whether the webhook is active. + + + + + Gets or sets the webhook's target. + + + + + Raised when data on the webhook is updated. + + + + + Creates a new instance of the object and registers a webhook with Trello. + + + + + (Optional) Custom authorization parameters. When not provided, + will be used. + + + + Creates a new instance of the object for a webhook which has already been registered with Trello. + + + (Optional) Custom authorization parameters. When not provided, + will be used. + + + + Deletes the webhook. + + + This permanently deletes the card from Trello's server, however, this object will + remain in memory and all properties will remain accessible. + + + + + Marks the webhook to be refreshed the next time data is accessed. + + + + + Describes dependency between method input and output. + + +

Function Definition Table syntax:

+ + FDT ::= FDTRow [;FDTRow]* + FDTRow ::= Input => Output | Output <= Input + Input ::= ParameterName: Value [, Input]* + Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + Value ::= true | false | null | notnull | canbenull + + If method has single input parameter, it's name could be omitted.
+ Using halt (or void/nothing, which is the same) + for method output means that the methos doesn't return normally.
+ canbenull annotation is only applicable for output parameters.
+ You can use multiple [ContractAnnotation] for each FDT row, + or use single attribute with rows separated by semicolon.
+
+ + + [ContractAnnotation("=> halt")] + public void TerminationMethod() + + + [ContractAnnotation("halt <= condition: false")] + public void Assert(bool condition, string text) // regular assertion method + + + [ContractAnnotation("s:null => true")] + public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + + + // A method that returns null if the parameter is null, + // and not null if the parameter is not null + [ContractAnnotation("null => null; notnull => notnull")] + public object Transform(object data) + + + [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] + public bool TryParse(string s, out Person result) + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + Indicates that the value of the marked element could never be null. + + + [NotNull] object Foo() { + return null; // Warning: Possible 'null' assignment + } + + +
+
diff --git a/WizBot/bin/Debug/Newtonsoft.Json.xml b/WizBot/bin/Debug/Newtonsoft.Json.xml new file mode 100644 index 000000000..3a432dacb --- /dev/null +++ b/WizBot/bin/Debug/Newtonsoft.Json.xml @@ -0,0 +1,9102 @@ + + + + Newtonsoft.Json + + + + + Represents a BSON Oid (object id). + + + + + Gets or sets the value of the Oid. + + The value of the Oid. + + + + Initializes a new instance of the class. + + The Oid value. + + + + Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. + + + + + Gets or sets a value indicating whether binary data reading should compatible with incorrect Json.NET 3.5 written binary. + + + true if binary data reading will be compatible with incorrect Json.NET 3.5 written binary; otherwise, false. + + + + + Gets or sets a value indicating whether the root object will be read as a JSON array. + + + true if the root object will be read as a JSON array; otherwise, false. + + + + + Gets or sets the used when reading values from BSON. + + The used when reading values from BSON. + + + + Initializes a new instance of the class. + + The stream. + + + + Initializes a new instance of the class. + + The reader. + + + + Initializes a new instance of the class. + + The stream. + if set to true the root object will be read as a JSON array. + The used when reading values from BSON. + + + + Initializes a new instance of the class. + + The reader. + if set to true the root object will be read as a JSON array. + The used when reading values from BSON. + + + + Reads the next JSON token from the stream. + + + true if the next token was read successfully; false if there are no more tokens to read. + + + + + Changes the to Closed. + + + + + Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. + + + + + Gets or sets the used when writing values to BSON. + When set to no conversion will occur. + + The used when writing values to BSON. + + + + Initializes a new instance of the class. + + The stream. + + + + Initializes a new instance of the class. + + The writer. + + + + Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. + + + + + Writes the end. + + The token. + + + + Writes out a comment /*...*/ containing the specified text. + + Text to place inside the comment. + + + + Writes the start of a constructor with the given name. + + The name of the constructor. + + + + Writes raw JSON. + + The raw JSON to write. + + + + Writes raw JSON where a value is expected and updates the writer's state. + + The raw JSON to write. + + + + Writes the beginning of a JSON array. + + + + + Writes the beginning of a JSON object. + + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + + + + Closes this stream and the underlying stream. + + + + + Writes a value. + An error will raised if the value cannot be written as a single JSON token. + + The value to write. + + + + Writes a null value. + + + + + Writes an undefined value. + + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value. + + The [] value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value that represents a BSON object id. + + The Object ID value to write. + + + + Writes a BSON regex. + + The regex pattern. + The regex options. + + + + Specifies how constructors are used when initializing objects during deserialization by the . + + + + + First attempt to use the public default constructor, then fall back to single paramatized constructor, then the non-public default constructor. + + + + + Json.NET will use a non-public default constructor before falling back to a paramatized constructor. + + + + + Converts a binary value to and from a base 64 string value. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from JSON and BSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Create a custom object + + The object type to convert. + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Creates an object which will then be populated by the serializer. + + Type of the object. + The created object. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Gets a value indicating whether this can write JSON. + + + true if this can write JSON; otherwise, false. + + + + + Converts a to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified value type. + + Type of the value. + + true if this instance can convert the specified value type; otherwise, false. + + + + + Converts a to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified value type. + + Type of the value. + + true if this instance can convert the specified value type; otherwise, false. + + + + + Provides a base class for converting a to and from JSON. + + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a F# discriminated union type to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts an Entity Framework EntityKey to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts an ExpandoObject to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Gets a value indicating whether this can write JSON. + + + true if this can write JSON; otherwise, false. + + + + + Converts a to and from the ISO 8601 date format (e.g. 2008-04-12T12:53Z). + + + + + Gets or sets the date time styles used when converting a date to and from JSON. + + The date time styles used when converting a date to and from JSON. + + + + Gets or sets the date time format used when converting a date to and from JSON. + + The date time format used when converting a date to and from JSON. + + + + Gets or sets the culture used when converting a date to and from JSON. + + The culture used when converting a date to and from JSON. + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Converts a to and from a JavaScript date constructor (e.g. new Date(52231943)). + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing property value of the JSON that is being converted. + The calling serializer. + The object value. + + + + Converts a to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from JSON and BSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts an to and from its name string value. + + + + + Gets or sets a value indicating whether the written enum text should be camel case. + + true if the written enum text will be camel case; otherwise, false. + + + + Gets or sets a value indicating whether integer values are allowed. + + true if integers are allowed; otherwise, false. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + true if the written enum text will be camel case; otherwise, false. + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts a to and from a string (e.g. "1.2.3.4"). + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing property value of the JSON that is being converted. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + Converts XML to and from JSON. + + + + + Gets or sets the name of the root element to insert when deserializing to XML if the JSON structure has produces multiple root elements. + + The name of the deserialize root element. + + + + Gets or sets a flag to indicate whether to write the Json.NET array attribute. + This attribute helps preserve arrays when converting the written XML back to JSON. + + true if the array attibute is written to the XML; otherwise, false. + + + + Gets or sets a value indicating whether to write the root JSON object. + + true if the JSON root object is omitted; otherwise, false. + + + + Writes the JSON representation of the object. + + The to write to. + The calling serializer. + The value. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Checks if the attributeName is a namespace attribute. + + Attribute name to test. + The attribute name prefix if it has one, otherwise an empty string. + True if attribute name is for a namespace attribute, otherwise false. + + + + Determines whether this instance can convert the specified value type. + + Type of the value. + + true if this instance can convert the specified value type; otherwise, false. + + + + + Specifies how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. + + + + + Floating point numbers are parsed to . + + + + + Floating point numbers are parsed to . + + + + + Specifies how dates are formatted when writing JSON text. + + + + + Dates are written in the ISO 8601 format, e.g. "2012-03-21T05:40Z". + + + + + Dates are written in the Microsoft JSON format, e.g. "\/Date(1198908717056)\/". + + + + + Specifies how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON text. + + + + + Date formatted strings are not parsed to a date type and are read as strings. + + + + + Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . + + + + + Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . + + + + + Specifies how to treat the time value when converting between string and . + + + + + Treat as local time. If the object represents a Coordinated Universal Time (UTC), it is converted to the local time. + + + + + Treat as a UTC. If the object represents a local time, it is converted to a UTC. + + + + + Treat as a local time if a is being converted to a string. + If a string is being converted to , convert to a local time if a time zone is specified. + + + + + Time zone information should be preserved when converting. + + + + + Specifies default value handling options for the . + + + + + + + + + Include members where the member value is the same as the member's default value when serializing objects. + Included members are written to JSON. Has no effect when deserializing. + + + + + Ignore members where the member value is the same as the member's default value when serializing objects + so that is is not written to JSON. + This option will ignore all default values (e.g. null for objects and nullable types; 0 for integers, + decimals and floating point numbers; and false for booleans). The default value ignored can be changed by + placing the on the property. + + + + + Members with a default value but no JSON will be set to their default value when deserializing. + + + + + Ignore members where the member value is the same as the member's default value when serializing objects + and sets members to their default value when deserializing. + + + + + Specifies float format handling options when writing special floating point numbers, e.g. , + and with . + + + + + Write special floating point values as strings in JSON, e.g. "NaN", "Infinity", "-Infinity". + + + + + Write special floating point values as symbols in JSON, e.g. NaN, Infinity, -Infinity. + Note that this will produce non-valid JSON. + + + + + Write special floating point values as the property's default value in JSON, e.g. 0.0 for a property, null for a property. + + + + + Specifies formatting options for the . + + + + + No special formatting is applied. This is the default. + + + + + Causes child objects to be indented according to the and settings. + + + + + Provides an interface for using pooled arrays. + + The array type content. + + + + Rent a array from the pool. This array must be returned when it is no longer needed. + + The minimum required length of the array. The returned array may be longer. + The rented array from the pool. This array must be returned when it is no longer needed. + + + + Return an array to the pool. + + The array that is being returned. + + + + Provides an interface to enable a class to return line and position information. + + + + + Gets a value indicating whether the class can return line information. + + + true if LineNumber and LinePosition can be provided; otherwise, false. + + + + + Gets the current line number. + + The current line number or 0 if no line information is available (for example, HasLineInfo returns false). + + + + Gets the current line position. + + The current line position or 0 if no line information is available (for example, HasLineInfo returns false). + + + + Instructs the how to serialize the collection. + + + + + Gets or sets a value indicating whether null items are allowed in the collection. + + true if null items are allowed in the collection; otherwise, false. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with a flag indicating whether the array can contain null items + + A flag indicating whether the array can contain null items. + + + + Initializes a new instance of the class with the specified container Id. + + The container Id. + + + + Instructs the to use the specified constructor when deserializing that object. + + + + + Instructs the how to serialize the object. + + + + + Gets or sets the id. + + The id. + + + + Gets or sets the title. + + The title. + + + + Gets or sets the description. + + The description. + + + + Gets the collection's items converter. + + The collection's items converter. + + + + The parameter list to use when constructing the JsonConverter described by ItemConverterType. + If null, the default constructor is used. + When non-null, there must be a constructor defined in the JsonConverter that exactly matches the number, + order, and type of these parameters. + + + [JsonContainer(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] + + + + + Gets or sets a value that indicates whether to preserve object references. + + + true to keep object reference; otherwise, false. The default is false. + + + + + Gets or sets a value that indicates whether to preserve collection's items references. + + + true to keep collection's items object references; otherwise, false. The default is false. + + + + + Gets or sets the reference loop handling used when serializing the collection's items. + + The reference loop handling. + + + + Gets or sets the type name handling used when serializing the collection's items. + + The type name handling. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified container Id. + + The container Id. + + + + Provides methods for converting between common language runtime types and JSON types. + + + + + + + + Gets or sets a function that creates default . + Default settings are automatically used by serialization methods on , + and and on . + To serialize without using any default settings create a with + . + + + + + Represents JavaScript's boolean value true as a string. This field is read-only. + + + + + Represents JavaScript's boolean value false as a string. This field is read-only. + + + + + Represents JavaScript's null as a string. This field is read-only. + + + + + Represents JavaScript's undefined as a string. This field is read-only. + + + + + Represents JavaScript's positive infinity as a string. This field is read-only. + + + + + Represents JavaScript's negative infinity as a string. This field is read-only. + + + + + Represents JavaScript's NaN as a string. This field is read-only. + + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation using the specified. + + The value to convert. + The format the date will be converted to. + The time zone handling when the date is converted to a string. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation using the specified. + + The value to convert. + The format the date will be converted to. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + The string delimiter character. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + The string delimiter character. + The string escape handling. + A JSON string representation of the . + + + + Converts the to its JSON string representation. + + The value to convert. + A JSON string representation of the . + + + + Serializes the specified object to a JSON string. + + The object to serialize. + A JSON string representation of the object. + + + + Serializes the specified object to a JSON string using formatting. + + The object to serialize. + Indicates how the output is formatted. + + A JSON string representation of the object. + + + + + Serializes the specified object to a JSON string using a collection of . + + The object to serialize. + A collection converters used while serializing. + A JSON string representation of the object. + + + + Serializes the specified object to a JSON string using formatting and a collection of . + + The object to serialize. + Indicates how the output is formatted. + A collection converters used while serializing. + A JSON string representation of the object. + + + + Serializes the specified object to a JSON string using . + + The object to serialize. + The used to serialize the object. + If this is null, default serialization settings will be used. + + A JSON string representation of the object. + + + + + Serializes the specified object to a JSON string using a type, formatting and . + + The object to serialize. + The used to serialize the object. + If this is null, default serialization settings will be used. + + The type of the value being serialized. + This parameter is used when is Auto to write out the type name if the type of the value does not match. + Specifing the type is optional. + + + A JSON string representation of the object. + + + + + Serializes the specified object to a JSON string using formatting and . + + The object to serialize. + Indicates how the output is formatted. + The used to serialize the object. + If this is null, default serialization settings will be used. + + A JSON string representation of the object. + + + + + Serializes the specified object to a JSON string using a type, formatting and . + + The object to serialize. + Indicates how the output is formatted. + The used to serialize the object. + If this is null, default serialization settings will be used. + + The type of the value being serialized. + This parameter is used when is Auto to write out the type name if the type of the value does not match. + Specifing the type is optional. + + + A JSON string representation of the object. + + + + + Asynchronously serializes the specified object to a JSON string. + Serialization will happen on a new thread. + + The object to serialize. + + A task that represents the asynchronous serialize operation. The value of the TResult parameter contains a JSON string representation of the object. + + + + + Asynchronously serializes the specified object to a JSON string using formatting. + Serialization will happen on a new thread. + + The object to serialize. + Indicates how the output is formatted. + + A task that represents the asynchronous serialize operation. The value of the TResult parameter contains a JSON string representation of the object. + + + + + Asynchronously serializes the specified object to a JSON string using formatting and a collection of . + Serialization will happen on a new thread. + + The object to serialize. + Indicates how the output is formatted. + The used to serialize the object. + If this is null, default serialization settings will be used. + + A task that represents the asynchronous serialize operation. The value of the TResult parameter contains a JSON string representation of the object. + + + + + Deserializes the JSON to a .NET object. + + The JSON to deserialize. + The deserialized object from the JSON string. + + + + Deserializes the JSON to a .NET object using . + + The JSON to deserialize. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type. + + The JSON to deserialize. + The of object being deserialized. + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type. + + The type of the object to deserialize to. + The JSON to deserialize. + The deserialized object from the JSON string. + + + + Deserializes the JSON to the given anonymous type. + + + The anonymous type to deserialize to. This can't be specified + traditionally and must be infered from the anonymous type passed + as a parameter. + + The JSON to deserialize. + The anonymous type object. + The deserialized anonymous type from the JSON string. + + + + Deserializes the JSON to the given anonymous type using . + + + The anonymous type to deserialize to. This can't be specified + traditionally and must be infered from the anonymous type passed + as a parameter. + + The JSON to deserialize. + The anonymous type object. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + The deserialized anonymous type from the JSON string. + + + + Deserializes the JSON to the specified .NET type using a collection of . + + The type of the object to deserialize to. + The JSON to deserialize. + Converters to use while deserializing. + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type using . + + The type of the object to deserialize to. + The object to deserialize. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type using a collection of . + + The JSON to deserialize. + The type of the object to deserialize. + Converters to use while deserializing. + The deserialized object from the JSON string. + + + + Deserializes the JSON to the specified .NET type using . + + The JSON to deserialize. + The type of the object to deserialize to. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + The deserialized object from the JSON string. + + + + Asynchronously deserializes the JSON to the specified .NET type. + Deserialization will happen on a new thread. + + The type of the object to deserialize to. + The JSON to deserialize. + + A task that represents the asynchronous deserialize operation. The value of the TResult parameter contains the deserialized object from the JSON string. + + + + + Asynchronously deserializes the JSON to the specified .NET type using . + Deserialization will happen on a new thread. + + The type of the object to deserialize to. + The JSON to deserialize. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + + A task that represents the asynchronous deserialize operation. The value of the TResult parameter contains the deserialized object from the JSON string. + + + + + Asynchronously deserializes the JSON to the specified .NET type. + Deserialization will happen on a new thread. + + The JSON to deserialize. + + A task that represents the asynchronous deserialize operation. The value of the TResult parameter contains the deserialized object from the JSON string. + + + + + Asynchronously deserializes the JSON to the specified .NET type using . + Deserialization will happen on a new thread. + + The JSON to deserialize. + The type of the object to deserialize to. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + + A task that represents the asynchronous deserialize operation. The value of the TResult parameter contains the deserialized object from the JSON string. + + + + + Populates the object with values from the JSON string. + + The JSON to populate values from. + The target object to populate values onto. + + + + Populates the object with values from the JSON string using . + + The JSON to populate values from. + The target object to populate values onto. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + + + + Asynchronously populates the object with values from the JSON string using . + + The JSON to populate values from. + The target object to populate values onto. + + The used to deserialize the object. + If this is null, default serialization settings will be used. + + + A task that represents the asynchronous populate operation. + + + + + Serializes the XML node to a JSON string. + + The node to serialize. + A JSON string of the XmlNode. + + + + Serializes the XML node to a JSON string using formatting. + + The node to serialize. + Indicates how the output is formatted. + A JSON string of the XmlNode. + + + + Serializes the XML node to a JSON string using formatting and omits the root object if is true. + + The node to serialize. + Indicates how the output is formatted. + Omits writing the root object. + A JSON string of the XmlNode. + + + + Deserializes the XmlNode from a JSON string. + + The JSON string. + The deserialized XmlNode + + + + Deserializes the XmlNode from a JSON string nested in a root elment specified by . + + The JSON string. + The name of the root element to append when deserializing. + The deserialized XmlNode + + + + Deserializes the XmlNode from a JSON string nested in a root elment specified by + and writes a .NET array attribute for collections. + + The JSON string. + The name of the root element to append when deserializing. + + A flag to indicate whether to write the Json.NET array attribute. + This attribute helps preserve arrays when converting the written XML back to JSON. + + The deserialized XmlNode + + + + Serializes the to a JSON string. + + The node to convert to JSON. + A JSON string of the XNode. + + + + Serializes the to a JSON string using formatting. + + The node to convert to JSON. + Indicates how the output is formatted. + A JSON string of the XNode. + + + + Serializes the to a JSON string using formatting and omits the root object if is true. + + The node to serialize. + Indicates how the output is formatted. + Omits writing the root object. + A JSON string of the XNode. + + + + Deserializes the from a JSON string. + + The JSON string. + The deserialized XNode + + + + Deserializes the from a JSON string nested in a root elment specified by . + + The JSON string. + The name of the root element to append when deserializing. + The deserialized XNode + + + + Deserializes the from a JSON string nested in a root elment specified by + and writes a .NET array attribute for collections. + + The JSON string. + The name of the root element to append when deserializing. + + A flag to indicate whether to write the Json.NET array attribute. + This attribute helps preserve arrays when converting the written XML back to JSON. + + The deserialized XNode + + + + Converts an object to and from JSON. + + + + + Writes the JSON representation of the object. + + The to write to. + The value. + The calling serializer. + + + + Reads the JSON representation of the object. + + The to read from. + Type of the object. + The existing value of object being read. + The calling serializer. + The object value. + + + + Determines whether this instance can convert the specified object type. + + Type of the object. + + true if this instance can convert the specified object type; otherwise, false. + + + + + + Gets the of the JSON produced by the JsonConverter. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The of the JSON produced by the JsonConverter. + + + + Gets a value indicating whether this can read JSON. + + true if this can read JSON; otherwise, false. + + + + Gets a value indicating whether this can write JSON. + + true if this can write JSON; otherwise, false. + + + + Instructs the to use the specified when serializing the member or class. + + + + + Gets the of the converter. + + The of the converter. + + + + The parameter list to use when constructing the JsonConverter described by ConverterType. + If null, the default constructor is used. + + + + + Initializes a new instance of the class. + + Type of the converter. + + + + Initializes a new instance of the class. + + Type of the converter. + Parameter list to use when constructing the JsonConverter. Can be null. + + + + Represents a collection of . + + + + + Instructs the how to serialize the collection. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified container Id. + + The container Id. + + + + The exception thrown when an error occurs during JSON serialization or deserialization. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + Instructs the to deserialize properties with no matching class member into the specified collection + and write values during serialization. + + + + + Gets or sets a value that indicates whether to write extension data when serializing the object. + + + true to write extension data when serializing the object; otherwise, false. The default is true. + + + + + Gets or sets a value that indicates whether to read extension data when deserializing the object. + + + true to read extension data when deserializing the object; otherwise, false. The default is true. + + + + + Initializes a new instance of the class. + + + + + Instructs the not to serialize the public field or public read/write property value. + + + + + Instructs the how to serialize the object. + + + + + Gets or sets the member serialization. + + The member serialization. + + + + Gets or sets a value that indicates whether the object's properties are required. + + + A value indicating whether the object's properties are required. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified member serialization. + + The member serialization. + + + + Initializes a new instance of the class with the specified container Id. + + The container Id. + + + + Instructs the to always serialize the member with the specified name. + + + + + Gets or sets the converter used when serializing the property's collection items. + + The collection's items converter. + + + + The parameter list to use when constructing the JsonConverter described by ItemConverterType. + If null, the default constructor is used. + When non-null, there must be a constructor defined in the JsonConverter that exactly matches the number, + order, and type of these parameters. + + + [JsonProperty(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] + + + + + Gets or sets the null value handling used when serializing this property. + + The null value handling. + + + + Gets or sets the default value handling used when serializing this property. + + The default value handling. + + + + Gets or sets the reference loop handling used when serializing this property. + + The reference loop handling. + + + + Gets or sets the object creation handling used when deserializing this property. + + The object creation handling. + + + + Gets or sets the type name handling used when serializing this property. + + The type name handling. + + + + Gets or sets whether this property's value is serialized as a reference. + + Whether this property's value is serialized as a reference. + + + + Gets or sets the order of serialization of a member. + + The numeric order of serialization. + + + + Gets or sets a value indicating whether this property is required. + + + A value indicating whether this property is required. + + + + + Gets or sets the name of the property. + + The name of the property. + + + + Gets or sets the the reference loop handling used when serializing the property's collection items. + + The collection's items reference loop handling. + + + + Gets or sets the the type name handling used when serializing the property's collection items. + + The collection's items type name handling. + + + + Gets or sets whether this property's collection items are serialized as a reference. + + Whether this property's collection items are serialized as a reference. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class with the specified name. + + Name of the property. + + + + Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. + + + + + Specifies the state of the reader. + + + + + The Read method has not been called. + + + + + The end of the file has been reached successfully. + + + + + Reader is at a property. + + + + + Reader is at the start of an object. + + + + + Reader is in an object. + + + + + Reader is at the start of an array. + + + + + Reader is in an array. + + + + + The Close method has been called. + + + + + Reader has just read a value. + + + + + Reader is at the start of a constructor. + + + + + Reader in a constructor. + + + + + An error occurred that prevents the read operation from continuing. + + + + + The end of the file has been reached successfully. + + + + + Gets the current reader state. + + The current reader state. + + + + Gets or sets a value indicating whether the underlying stream or + should be closed when the reader is closed. + + + true to close the underlying stream or when + the reader is closed; otherwise false. The default is true. + + + + + Gets or sets a value indicating whether multiple pieces of JSON content can + be read from a continuous stream without erroring. + + + true to support reading multiple pieces of JSON content; otherwise false. The default is false. + + + + + Gets the quotation mark character used to enclose the value of a string. + + + + + Get or set how time zones are handling when reading JSON. + + + + + Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + + + + + Get or set how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. + + + + + Get or set how custom date formatted strings are parsed when reading JSON. + + + + + Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . + + + + + Gets the type of the current JSON token. + + + + + Gets the text value of the current JSON token. + + + + + Gets The Common Language Runtime (CLR) type for the current JSON token. + + + + + Gets the depth of the current token in the JSON document. + + The depth of the current token in the JSON document. + + + + Gets the path of the current JSON token. + + + + + Gets or sets the culture used when reading JSON. Defaults to . + + + + + Initializes a new instance of the class with the specified . + + + + + Reads the next JSON token from the stream. + + true if the next token was read successfully; false if there are no more tokens to read. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a []. + + A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Skips the children of the current token. + + + + + Sets the current token. + + The new token. + + + + Sets the current token and value. + + The new token. + The value. + + + + Sets the state based on current token type. + + + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + + + Releases unmanaged and - optionally - managed resources + + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + + Changes the to Closed. + + + + + The exception thrown when an error occurs while reading JSON text. + + + + + Gets the line number indicating where the error occurred. + + The line number indicating where the error occurred. + + + + Gets the line position indicating where the error occurred. + + The line position indicating where the error occurred. + + + + Gets the path to the JSON where the error occurred. + + The path to the JSON where the error occurred. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + Instructs the to always serialize the member, and require the member has a value. + + + + + The exception thrown when an error occurs during JSON serialization or deserialization. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + Serializes and deserializes objects into and from the JSON format. + The enables you to control how objects are encoded into JSON. + + + + + Occurs when the errors during serialization and deserialization. + + + + + Gets or sets the used by the serializer when resolving references. + + + + + Gets or sets the used by the serializer when resolving type names. + + + + + Gets or sets the used by the serializer when writing trace messages. + + The trace writer. + + + + Gets or sets the equality comparer used by the serializer when comparing references. + + The equality comparer. + + + + Gets or sets how type name writing and reading is handled by the serializer. + + + should be used with caution when your application deserializes JSON from an external source. + Incoming types should be validated with a custom + when deserializing with a value other than TypeNameHandling.None. + + + + + Gets or sets how a type name assembly is written and resolved by the serializer. + + The type name assembly format. + + + + Gets or sets how object references are preserved by the serializer. + + + + + Get or set how reference loops (e.g. a class referencing itself) is handled. + + + + + Get or set how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. + + + + + Get or set how null values are handled during serialization and deserialization. + + + + + Get or set how null default are handled during serialization and deserialization. + + + + + Gets or sets how objects are created during deserialization. + + The object creation handling. + + + + Gets or sets how constructors are used during deserialization. + + The constructor handling. + + + + Gets or sets how metadata properties are used during deserialization. + + The metadata properties handling. + + + + Gets a collection that will be used during serialization. + + Collection that will be used during serialization. + + + + Gets or sets the contract resolver used by the serializer when + serializing .NET objects to JSON and vice versa. + + + + + Gets or sets the used by the serializer when invoking serialization callback methods. + + The context. + + + + Indicates how JSON text output is formatted. + + + + + Get or set how dates are written to JSON text. + + + + + Get or set how time zones are handling during serialization and deserialization. + + + + + Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + + + + + Get or set how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. + + + + + Get or set how special floating point numbers, e.g. , + and , + are written as JSON text. + + + + + Get or set how strings are escaped when writing JSON text. + + + + + Get or set how and values are formatted when writing JSON text, and the expected date format when reading JSON text. + + + + + Gets or sets the culture used when reading JSON. Defaults to . + + + + + Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . + + + + + Gets a value indicating whether there will be a check for additional JSON content after deserializing an object. + + + true if there will be a check for additional JSON content after deserializing an object; otherwise, false. + + + + + Initializes a new instance of the class. + + + + + Creates a new instance. + The will not use default settings + from . + + + A new instance. + The will not use default settings + from . + + + + + Creates a new instance using the specified . + The will not use default settings + from . + + The settings to be applied to the . + + A new instance using the specified . + The will not use default settings + from . + + + + + Creates a new instance. + The will use default settings + from . + + + A new instance. + The will use default settings + from . + + + + + Creates a new instance using the specified . + The will use default settings + from as well as the specified . + + The settings to be applied to the . + + A new instance using the specified . + The will use default settings + from as well as the specified . + + + + + Populates the JSON values onto the target object. + + The that contains the JSON structure to reader values from. + The target object to populate values onto. + + + + Populates the JSON values onto the target object. + + The that contains the JSON structure to reader values from. + The target object to populate values onto. + + + + Deserializes the JSON structure contained by the specified . + + The that contains the JSON structure to deserialize. + The being deserialized. + + + + Deserializes the JSON structure contained by the specified + into an instance of the specified type. + + The containing the object. + The of object being deserialized. + The instance of being deserialized. + + + + Deserializes the JSON structure contained by the specified + into an instance of the specified type. + + The containing the object. + The type of the object to deserialize. + The instance of being deserialized. + + + + Deserializes the JSON structure contained by the specified + into an instance of the specified type. + + The containing the object. + The of object being deserialized. + The instance of being deserialized. + + + + Serializes the specified and writes the JSON structure + to a Stream using the specified . + + The used to write the JSON structure. + The to serialize. + + + + Serializes the specified and writes the JSON structure + to a Stream using the specified . + + The used to write the JSON structure. + The to serialize. + + The type of the value being serialized. + This parameter is used when is Auto to write out the type name if the type of the value does not match. + Specifing the type is optional. + + + + + Serializes the specified and writes the JSON structure + to a Stream using the specified . + + The used to write the JSON structure. + The to serialize. + + The type of the value being serialized. + This parameter is used when is Auto to write out the type name if the type of the value does not match. + Specifing the type is optional. + + + + + Serializes the specified and writes the JSON structure + to a Stream using the specified . + + The used to write the JSON structure. + The to serialize. + + + + Specifies the settings on a object. + + + + + Gets or sets how reference loops (e.g. a class referencing itself) is handled. + + Reference loop handling. + + + + Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. + + Missing member handling. + + + + Gets or sets how objects are created during deserialization. + + The object creation handling. + + + + Gets or sets how null values are handled during serialization and deserialization. + + Null value handling. + + + + Gets or sets how null default are handled during serialization and deserialization. + + The default value handling. + + + + Gets or sets a collection that will be used during serialization. + + The converters. + + + + Gets or sets how object references are preserved by the serializer. + + The preserve references handling. + + + + Gets or sets how type name writing and reading is handled by the serializer. + + + should be used with caution when your application deserializes JSON from an external source. + Incoming types should be validated with a custom + when deserializing with a value other than TypeNameHandling.None. + + The type name handling. + + + + Gets or sets how metadata properties are used during deserialization. + + The metadata properties handling. + + + + Gets or sets how a type name assembly is written and resolved by the serializer. + + The type name assembly format. + + + + Gets or sets how constructors are used during deserialization. + + The constructor handling. + + + + Gets or sets the contract resolver used by the serializer when + serializing .NET objects to JSON and vice versa. + + The contract resolver. + + + + Gets or sets the equality comparer used by the serializer when comparing references. + + The equality comparer. + + + + Gets or sets the used by the serializer when resolving references. + + The reference resolver. + + + + Gets or sets a function that creates the used by the serializer when resolving references. + + A function that creates the used by the serializer when resolving references. + + + + Gets or sets the used by the serializer when writing trace messages. + + The trace writer. + + + + Gets or sets the used by the serializer when resolving type names. + + The binder. + + + + Gets or sets the error handler called during serialization and deserialization. + + The error handler called during serialization and deserialization. + + + + Gets or sets the used by the serializer when invoking serialization callback methods. + + The context. + + + + Get or set how and values are formatted when writing JSON text, and the expected date format when reading JSON text. + + + + + Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . + + + + + Indicates how JSON text output is formatted. + + + + + Get or set how dates are written to JSON text. + + + + + Get or set how time zones are handling during serialization and deserialization. + + + + + Get or set how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. + + + + + Get or set how special floating point numbers, e.g. , + and , + are written as JSON. + + + + + Get or set how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. + + + + + Get or set how strings are escaped when writing JSON text. + + + + + Gets or sets the culture used when reading JSON. Defaults to . + + + + + Gets a value indicating whether there will be a check for additional content after deserializing an object. + + + true if there will be a check for additional content after deserializing an object; otherwise, false. + + + + + Initializes a new instance of the class. + + + + + Represents a reader that provides fast, non-cached, forward-only access to JSON text data. + + + + + Initializes a new instance of the class with the specified . + + The TextReader containing the XML data to read. + + + + Gets or sets the reader's character buffer pool. + + + + + Reads the next JSON token from the stream. + + + true if the next token was read successfully; false if there are no more tokens to read. + + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a []. + + A [] or a null reference if the next JSON token is null. This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Changes the state to closed. + + + + + Gets a value indicating whether the class can return line information. + + + true if LineNumber and LinePosition can be provided; otherwise, false. + + + + + Gets the current line number. + + + The current line number or 0 if no line information is available (for example, HasLineInfo returns false). + + + + + Gets the current line position. + + + The current line position or 0 if no line information is available (for example, HasLineInfo returns false). + + + + + Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. + + + + + Gets or sets the writer's character array pool. + + + + + Gets or sets how many IndentChars to write for each level in the hierarchy when is set to Formatting.Indented. + + + + + Gets or sets which character to use to quote attribute values. + + + + + Gets or sets which character to use for indenting when is set to Formatting.Indented. + + + + + Gets or sets a value indicating whether object names will be surrounded with quotes. + + + + + Creates an instance of the JsonWriter class using the specified . + + The TextWriter to write to. + + + + Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. + + + + + Closes this stream and the underlying stream. + + + + + Writes the beginning of a JSON object. + + + + + Writes the beginning of a JSON array. + + + + + Writes the start of a constructor with the given name. + + The name of the constructor. + + + + Writes the specified end token. + + The end token to write. + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + A flag to indicate whether the text should be escaped when it is written as a JSON property name. + + + + Writes indent characters. + + + + + Writes the JSON value delimiter. + + + + + Writes an indent space. + + + + + Writes a value. + An error will raised if the value cannot be written as a single JSON token. + + The value to write. + + + + Writes a null value. + + + + + Writes an undefined value. + + + + + Writes raw JSON. + + The raw JSON to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value. + + The [] value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes out a comment /*...*/ containing the specified text. + + Text to place inside the comment. + + + + Writes out the given white space. + + The string of white space characters. + + + + Specifies the type of JSON token. + + + + + This is returned by the if a method has not been called. + + + + + An object start token. + + + + + An array start token. + + + + + A constructor start token. + + + + + An object property name. + + + + + A comment. + + + + + Raw JSON. + + + + + An integer. + + + + + A float. + + + + + A string. + + + + + A boolean. + + + + + A null token. + + + + + An undefined token. + + + + + An object end token. + + + + + An array end token. + + + + + A constructor end token. + + + + + A Date. + + + + + Byte data. + + + + + + Represents a reader that provides validation. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Sets an event handler for receiving schema validation errors. + + + + + Gets the text value of the current JSON token. + + + + + + Gets the depth of the current token in the JSON document. + + The depth of the current token in the JSON document. + + + + Gets the path of the current JSON token. + + + + + Gets the quotation mark character used to enclose the value of a string. + + + + + + Gets the type of the current JSON token. + + + + + + Gets the Common Language Runtime (CLR) type for the current JSON token. + + + + + + Initializes a new instance of the class that + validates the content returned from the given . + + The to read from while validating. + + + + Gets or sets the schema. + + The schema. + + + + Gets the used to construct this . + + The specified in the constructor. + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream as a []. + + + A [] or a null reference if the next JSON token is null. + + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . This method will return null at the end of an array. + + + + Reads the next JSON token from the stream as a . + + A . + + + + Reads the next JSON token from the stream. + + + true if the next token was read successfully; false if there are no more tokens to read. + + + + + Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. + + + + + Gets or sets a value indicating whether the underlying stream or + should be closed when the writer is closed. + + + true to close the underlying stream or when + the writer is closed; otherwise false. The default is true. + + + + + Gets the top. + + The top. + + + + Gets the state of the writer. + + + + + Gets the path of the writer. + + + + + Indicates how JSON text output is formatted. + + + + + Get or set how dates are written to JSON text. + + + + + Get or set how time zones are handling when writing JSON text. + + + + + Get or set how strings are escaped when writing JSON text. + + + + + Get or set how special floating point numbers, e.g. , + and , + are written to JSON text. + + + + + Get or set how and values are formatting when writing JSON text. + + + + + Gets or sets the culture used when writing JSON. Defaults to . + + + + + Creates an instance of the JsonWriter class. + + + + + Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. + + + + + Closes this stream and the underlying stream. + + + + + Writes the beginning of a JSON object. + + + + + Writes the end of a JSON object. + + + + + Writes the beginning of a JSON array. + + + + + Writes the end of an array. + + + + + Writes the start of a constructor with the given name. + + The name of the constructor. + + + + Writes the end constructor. + + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + A flag to indicate whether the text should be escaped when it is written as a JSON property name. + + + + Writes the end of the current JSON object or array. + + + + + Writes the current token and its children. + + The to read the token from. + + + + Writes the current token. + + The to read the token from. + A flag indicating whether the current token's children should be written. + + + + Writes the token and its value. + + The to write. + + The value to write. + A value is only required for tokens that have an associated value, e.g. the property name for . + A null value can be passed to the method for token's that don't have a value, e.g. . + + + + Writes the token. + + The to write. + + + + Writes the specified end token. + + The end token to write. + + + + Writes indent characters. + + + + + Writes the JSON value delimiter. + + + + + Writes an indent space. + + + + + Writes a null value. + + + + + Writes an undefined value. + + + + + Writes raw JSON without changing the writer's state. + + The raw JSON to write. + + + + Writes raw JSON where a value is expected and updates the writer's state. + + The raw JSON to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value. + + The [] value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + An error will raised if the value cannot be written as a single JSON token. + + The value to write. + + + + Writes out a comment /*...*/ containing the specified text. + + Text to place inside the comment. + + + + Writes out the given white space. + + The string of white space characters. + + + + Releases unmanaged and - optionally - managed resources + + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + + Sets the state of the JsonWriter, + + The JsonToken being written. + The value being written. + + + + The exception thrown when an error occurs while reading JSON text. + + + + + Gets the path to the JSON where the error occurred. + + The path to the JSON where the error occurred. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + Specifies how JSON comments are handled when loading JSON. + + + + + Ignore comments. + + + + + Load comments as a with type . + + + + + Specifies how line information is handled when loading JSON. + + + + + Ignore line information. + + + + + Load line information. + + + + + Contains the LINQ to JSON extension methods. + + + + + Returns a collection of tokens that contains the ancestors of every token in the source collection. + + The type of the objects in source, constrained to . + An of that contains the source collection. + An of that contains the ancestors of every token in the source collection. + + + + Returns a collection of tokens that contains every token in the source collection, and the ancestors of every token in the source collection. + + The type of the objects in source, constrained to . + An of that contains the source collection. + An of that contains every token in the source collection, the ancestors of every token in the source collection. + + + + Returns a collection of tokens that contains the descendants of every token in the source collection. + + The type of the objects in source, constrained to . + An of that contains the source collection. + An of that contains the descendants of every token in the source collection. + + + + Returns a collection of tokens that contains every token in the source collection, and the descendants of every token in the source collection. + + The type of the objects in source, constrained to . + An of that contains the source collection. + An of that contains every token in the source collection, and the descendants of every token in the source collection. + + + + Returns a collection of child properties of every object in the source collection. + + An of that contains the source collection. + An of that contains the properties of every object in the source collection. + + + + Returns a collection of child values of every object in the source collection with the given key. + + An of that contains the source collection. + The token key. + An of that contains the values of every token in the source collection with the given key. + + + + Returns a collection of child values of every object in the source collection. + + An of that contains the source collection. + An of that contains the values of every token in the source collection. + + + + Returns a collection of converted child values of every object in the source collection with the given key. + + The type to convert the values to. + An of that contains the source collection. + The token key. + An that contains the converted values of every token in the source collection with the given key. + + + + Returns a collection of converted child values of every object in the source collection. + + The type to convert the values to. + An of that contains the source collection. + An that contains the converted values of every token in the source collection. + + + + Converts the value. + + The type to convert the value to. + A cast as a of . + A converted value. + + + + Converts the value. + + The source collection type. + The type to convert the value to. + A cast as a of . + A converted value. + + + + Returns a collection of child tokens of every array in the source collection. + + The source collection type. + An of that contains the source collection. + An of that contains the values of every token in the source collection. + + + + Returns a collection of converted child tokens of every array in the source collection. + + An of that contains the source collection. + The type to convert the values to. + The source collection type. + An that contains the converted values of every token in the source collection. + + + + Returns the input typed as . + + An of that contains the source collection. + The input typed as . + + + + Returns the input typed as . + + The source collection type. + An of that contains the source collection. + The input typed as . + + + + Represents a collection of objects. + + The type of token + + + + Gets the with the specified key. + + + + + + Represents a JSON array. + + + + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Gets the node type for this . + + The type. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class with the specified content. + + The contents of the array. + + + + Initializes a new instance of the class with the specified content. + + The contents of the array. + + + + Loads an from a . + + A that will be read for the content of the . + A that contains the JSON that was read from the specified . + + + + Loads an from a . + + A that will be read for the content of the . + The used to load the JSON. + If this is null, default load settings will be used. + A that contains the JSON that was read from the specified . + + + + Load a from a string that contains JSON. + + A that contains JSON. + A populated from the string that contains JSON. + + + + + + + Load a from a string that contains JSON. + + A that contains JSON. + The used to load the JSON. + If this is null, default load settings will be used. + A populated from the string that contains JSON. + + + + + + + Creates a from an object. + + The object that will be used to create . + A with the values of the specified object + + + + Creates a from an object. + + The object that will be used to create . + The that will be used to read the object. + A with the values of the specified object + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Gets the with the specified key. + + The with the specified key. + + + + Gets or sets the at the specified index. + + + + + + Determines the index of a specific item in the . + + The object to locate in the . + + The index of if found in the list; otherwise, -1. + + + + + Inserts an item to the at the specified index. + + The zero-based index at which should be inserted. + The object to insert into the . + + is not a valid index in the . + The is read-only. + + + + Removes the item at the specified index. + + The zero-based index of the item to remove. + + is not a valid index in the . + The is read-only. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + + + + Adds an item to the . + + The object to add to the . + The is read-only. + + + + Removes all items from the . + + The is read-only. + + + + Determines whether the contains a specific value. + + The object to locate in the . + + true if is found in the ; otherwise, false. + + + + + Copies to. + + The array. + Index of the array. + + + + Gets a value indicating whether the is read-only. + + true if the is read-only; otherwise, false. + + + + Removes the first occurrence of a specific object from the . + + The object to remove from the . + + true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + + The is read-only. + + + + Represents a JSON constructor. + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Gets or sets the name of this constructor. + + The constructor name. + + + + Gets the node type for this . + + The type. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class with the specified name and content. + + The constructor name. + The contents of the constructor. + + + + Initializes a new instance of the class with the specified name and content. + + The constructor name. + The contents of the constructor. + + + + Initializes a new instance of the class with the specified name. + + The constructor name. + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Gets the with the specified key. + + The with the specified key. + + + + Loads an from a . + + A that will be read for the content of the . + A that contains the JSON that was read from the specified . + + + + Loads an from a . + + A that will be read for the content of the . + The used to load the JSON. + If this is null, default load settings will be used. + A that contains the JSON that was read from the specified . + + + + Represents a token that can contain other tokens. + + + + + Occurs when the list changes or an item in the list changes. + + + + + Occurs before an item is added to the collection. + + + + + Occurs when the items list of the collection has changed, or the collection is reset. + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Raises the event. + + The instance containing the event data. + + + + Raises the event. + + The instance containing the event data. + + + + Raises the event. + + The instance containing the event data. + + + + Gets a value indicating whether this token has child tokens. + + + true if this token has child values; otherwise, false. + + + + + Get the first child token of this token. + + + A containing the first child token of the . + + + + + Get the last child token of this token. + + + A containing the last child token of the . + + + + + Returns a collection of the child tokens of this token, in document order. + + + An of containing the child tokens of this , in document order. + + + + + Returns a collection of the child values of this token, in document order. + + The type to convert the values to. + + A containing the child values of this , in document order. + + + + + Returns a collection of the descendant tokens for this token in document order. + + An containing the descendant tokens of the . + + + + Returns a collection of the tokens that contain this token, and all descendant tokens of this token, in document order. + + An containing this token, and all the descendant tokens of the . + + + + Adds the specified content as children of this . + + The content to be added. + + + + Adds the specified content as the first children of this . + + The content to be added. + + + + Creates an that can be used to add tokens to the . + + An that is ready to have content written to it. + + + + Replaces the children nodes of this token with the specified content. + + The content. + + + + Removes the child nodes from this token. + + + + + Merge the specified content into this . + + The content to be merged. + + + + Merge the specified content into this using . + + The content to be merged. + The used to merge the content. + + + + Gets the count of child JSON tokens. + + The count of child JSON tokens + + + + Represents a collection of objects. + + The type of token + + + + An empty collection of objects. + + + + + Initializes a new instance of the struct. + + The enumerable. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + + + + Gets the with the specified key. + + + + + + Determines whether the specified is equal to this instance. + + The to compare with this instance. + + true if the specified is equal to this instance; otherwise, false. + + + + + Determines whether the specified is equal to this instance. + + The to compare with this instance. + + true if the specified is equal to this instance; otherwise, false. + + + + + Returns a hash code for this instance. + + + A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + + + + + Represents a JSON object. + + + + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Occurs when a property value changes. + + + + + Occurs when a property value is changing. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class with the specified content. + + The contents of the object. + + + + Initializes a new instance of the class with the specified content. + + The contents of the object. + + + + Gets the node type for this . + + The type. + + + + Gets an of this object's properties. + + An of this object's properties. + + + + Gets a the specified name. + + The property name. + A with the specified name or null. + + + + Gets an of this object's property values. + + An of this object's property values. + + + + Gets the with the specified key. + + The with the specified key. + + + + Gets or sets the with the specified property name. + + + + + + Loads an from a . + + A that will be read for the content of the . + A that contains the JSON that was read from the specified . + + + + Loads an from a . + + A that will be read for the content of the . + The used to load the JSON. + If this is null, default load settings will be used. + A that contains the JSON that was read from the specified . + + + + Load a from a string that contains JSON. + + A that contains JSON. + A populated from the string that contains JSON. + + + + + + + Load a from a string that contains JSON. + + A that contains JSON. + The used to load the JSON. + If this is null, default load settings will be used. + A populated from the string that contains JSON. + + + + + + + Creates a from an object. + + The object that will be used to create . + A with the values of the specified object + + + + Creates a from an object. + + The object that will be used to create . + The that will be used to read the object. + A with the values of the specified object + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Gets the with the specified property name. + + Name of the property. + The with the specified property name. + + + + Gets the with the specified property name. + The exact property name will be searched for first and if no matching property is found then + the will be used to match a property. + + Name of the property. + One of the enumeration values that specifies how the strings will be compared. + The with the specified property name. + + + + Tries to get the with the specified property name. + The exact property name will be searched for first and if no matching property is found then + the will be used to match a property. + + Name of the property. + The value. + One of the enumeration values that specifies how the strings will be compared. + true if a value was successfully retrieved; otherwise, false. + + + + Adds the specified property name. + + Name of the property. + The value. + + + + Removes the property with the specified name. + + Name of the property. + true if item was successfully removed; otherwise, false. + + + + Tries the get value. + + Name of the property. + The value. + true if a value was successfully retrieved; otherwise, false. + + + + Returns an enumerator that iterates through the collection. + + + A that can be used to iterate through the collection. + + + + + Raises the event with the provided arguments. + + Name of the property. + + + + Raises the event with the provided arguments. + + Name of the property. + + + + Returns the properties for this instance of a component. + + + A that represents the properties for this component instance. + + + + + Returns the properties for this instance of a component using the attribute array as a filter. + + An array of type that is used as a filter. + + A that represents the filtered properties for this component instance. + + + + + Returns a collection of custom attributes for this instance of a component. + + + An containing the attributes for this object. + + + + + Returns the class name of this instance of a component. + + + The class name of the object, or null if the class does not have a name. + + + + + Returns the name of this instance of a component. + + + The name of the object, or null if the object does not have a name. + + + + + Returns a type converter for this instance of a component. + + + A that is the converter for this object, or null if there is no for this object. + + + + + Returns the default event for this instance of a component. + + + An that represents the default event for this object, or null if this object does not have events. + + + + + Returns the default property for this instance of a component. + + + A that represents the default property for this object, or null if this object does not have properties. + + + + + Returns an editor of the specified type for this instance of a component. + + A that represents the editor for this object. + + An of the specified type that is the editor for this object, or null if the editor cannot be found. + + + + + Returns the events for this instance of a component using the specified attribute array as a filter. + + An array of type that is used as a filter. + + An that represents the filtered events for this component instance. + + + + + Returns the events for this instance of a component. + + + An that represents the events for this component instance. + + + + + Returns an object that contains the property described by the specified property descriptor. + + A that represents the property whose owner is to be found. + + An that represents the owner of the specified property. + + + + + Returns the responsible for binding operations performed on this object. + + The expression tree representation of the runtime value. + + The to bind this object. + + + + + Specifies the settings used when merging JSON. + + + + + Gets or sets the method used when merging JSON arrays. + + The method used when merging JSON arrays. + + + + Gets or sets how how null value properties are merged. + + How null value properties are merged. + + + + Represents a JSON property. + + + + + Gets the container's children tokens. + + The container's children tokens. + + + + Gets the property name. + + The property name. + + + + Gets or sets the property value. + + The property value. + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Gets the node type for this . + + The type. + + + + Initializes a new instance of the class. + + The property name. + The property content. + + + + Initializes a new instance of the class. + + The property name. + The property content. + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Loads an from a . + + A that will be read for the content of the . + A that contains the JSON that was read from the specified . + + + + Loads an from a . + + A that will be read for the content of the . + The used to load the JSON. + If this is null, default load settings will be used. + A that contains the JSON that was read from the specified . + + + + Represents a view of a . + + + + + Initializes a new instance of the class. + + The name. + + + + When overridden in a derived class, returns whether resetting an object changes its value. + + + true if resetting the component changes its value; otherwise, false. + + The component to test for reset capability. + + + + + When overridden in a derived class, gets the current value of the property on a component. + + + The value of a property for a given component. + + The component with the property for which to retrieve the value. + + + + + When overridden in a derived class, resets the value for this property of the component to the default value. + + The component with the property value that is to be reset to the default value. + + + + + When overridden in a derived class, sets the value of the component to a different value. + + The component with the property value that is to be set. + The new value. + + + + + When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted. + + + true if the property should be persisted; otherwise, false. + + The component with the property to be examined for persistence. + + + + + When overridden in a derived class, gets the type of the component this property is bound to. + + + A that represents the type of component this property is bound to. When the or methods are invoked, the object specified might be an instance of this type. + + + + + When overridden in a derived class, gets a value indicating whether this property is read-only. + + + true if the property is read-only; otherwise, false. + + + + + When overridden in a derived class, gets the type of the property. + + + A that represents the type of the property. + + + + + Gets the hash code for the name of the member. + + + + The hash code for the name of the member. + + + + + Represents a raw JSON string. + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class. + + The raw json. + + + + Creates an instance of with the content of the reader's current token. + + The reader. + An instance of with the content of the reader's current token. + + + + Represents an abstract JSON token. + + + + + Gets a comparer that can compare two tokens for value equality. + + A that can compare two nodes for value equality. + + + + Gets or sets the parent. + + The parent. + + + + Gets the root of this . + + The root of this . + + + + Gets the node type for this . + + The type. + + + + Gets a value indicating whether this token has child tokens. + + + true if this token has child values; otherwise, false. + + + + + Compares the values of two tokens, including the values of all descendant tokens. + + The first to compare. + The second to compare. + true if the tokens are equal; otherwise false. + + + + Gets the next sibling token of this node. + + The that contains the next sibling token. + + + + Gets the previous sibling token of this node. + + The that contains the previous sibling token. + + + + Gets the path of the JSON token. + + + + + Adds the specified content immediately after this token. + + A content object that contains simple content or a collection of content objects to be added after this token. + + + + Adds the specified content immediately before this token. + + A content object that contains simple content or a collection of content objects to be added before this token. + + + + Returns a collection of the ancestor tokens of this token. + + A collection of the ancestor tokens of this token. + + + + Returns a collection of tokens that contain this token, and the ancestors of this token. + + A collection of tokens that contain this token, and the ancestors of this token. + + + + Returns a collection of the sibling tokens after this token, in document order. + + A collection of the sibling tokens after this tokens, in document order. + + + + Returns a collection of the sibling tokens before this token, in document order. + + A collection of the sibling tokens before this token, in document order. + + + + Gets the with the specified key. + + The with the specified key. + + + + Gets the with the specified key converted to the specified type. + + The type to convert the token to. + The token key. + The converted token value. + + + + Get the first child token of this token. + + A containing the first child token of the . + + + + Get the last child token of this token. + + A containing the last child token of the . + + + + Returns a collection of the child tokens of this token, in document order. + + An of containing the child tokens of this , in document order. + + + + Returns a collection of the child tokens of this token, in document order, filtered by the specified type. + + The type to filter the child tokens on. + A containing the child tokens of this , in document order. + + + + Returns a collection of the child values of this token, in document order. + + The type to convert the values to. + A containing the child values of this , in document order. + + + + Removes this token from its parent. + + + + + Replaces this token with the specified token. + + The value. + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Returns the indented JSON for this token. + + + The indented JSON for this token. + + + + + Returns the JSON for this token using the given formatting and converters. + + Indicates how the output is formatted. + A collection of which will be used when writing the token. + The JSON for this token using the given formatting and converters. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to []. + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an explicit conversion from to . + + The value. + The result of the conversion. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from [] to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Performs an implicit conversion from to . + + The value to create a from. + The initialized with the specified value. + + + + Creates an for this token. + + An that can be used to read this token and its descendants. + + + + Creates a from an object. + + The object that will be used to create . + A with the value of the specified object + + + + Creates a from an object using the specified . + + The object that will be used to create . + The that will be used when reading the object. + A with the value of the specified object + + + + Creates the specified .NET type from the . + + The object type that the token will be deserialized to. + The new object created from the JSON value. + + + + Creates the specified .NET type from the . + + The object type that the token will be deserialized to. + The new object created from the JSON value. + + + + Creates the specified .NET type from the using the specified . + + The object type that the token will be deserialized to. + The that will be used when creating the object. + The new object created from the JSON value. + + + + Creates the specified .NET type from the using the specified . + + The object type that the token will be deserialized to. + The that will be used when creating the object. + The new object created from the JSON value. + + + + Creates a from a . + + An positioned at the token to read into this . + + An that contains the token and its descendant tokens + that were read from the reader. The runtime type of the token is determined + by the token type of the first token encountered in the reader. + + + + + Creates a from a . + + An positioned at the token to read into this . + The used to load the JSON. + If this is null, default load settings will be used. + + An that contains the token and its descendant tokens + that were read from the reader. The runtime type of the token is determined + by the token type of the first token encountered in the reader. + + + + + Load a from a string that contains JSON. + + A that contains JSON. + A populated from the string that contains JSON. + + + + Load a from a string that contains JSON. + + A that contains JSON. + The used to load the JSON. + If this is null, default load settings will be used. + A populated from the string that contains JSON. + + + + Creates a from a . + + An positioned at the token to read into this . + The used to load the JSON. + If this is null, default load settings will be used. + + An that contains the token and its descendant tokens + that were read from the reader. The runtime type of the token is determined + by the token type of the first token encountered in the reader. + + + + + Creates a from a . + + An positioned at the token to read into this . + + An that contains the token and its descendant tokens + that were read from the reader. The runtime type of the token is determined + by the token type of the first token encountered in the reader. + + + + + Selects a using a JPath expression. Selects the token that matches the object path. + + + A that contains a JPath expression. + + A , or null. + + + + Selects a using a JPath expression. Selects the token that matches the object path. + + + A that contains a JPath expression. + + A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + A . + + + + Selects a collection of elements using a JPath expression. + + + A that contains a JPath expression. + + An that contains the selected elements. + + + + Selects a collection of elements using a JPath expression. + + + A that contains a JPath expression. + + A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. + An that contains the selected elements. + + + + Returns the responsible for binding operations performed on this object. + + The expression tree representation of the runtime value. + + The to bind this object. + + + + + Returns the responsible for binding operations performed on this object. + + The expression tree representation of the runtime value. + + The to bind this object. + + + + + Creates a new instance of the . All child tokens are recursively cloned. + + A new instance of the . + + + + Adds an object to the annotation list of this . + + The annotation to add. + + + + Get the first annotation object of the specified type from this . + + The type of the annotation to retrieve. + The first annotation object that matches the specified type, or null if no annotation is of the specified type. + + + + Gets the first annotation object of the specified type from this . + + The of the annotation to retrieve. + The first annotation object that matches the specified type, or null if no annotation is of the specified type. + + + + Gets a collection of annotations of the specified type for this . + + The type of the annotations to retrieve. + An that contains the annotations for this . + + + + Gets a collection of annotations of the specified type for this . + + The of the annotations to retrieve. + An of that contains the annotations that match the specified type for this . + + + + Removes the annotations of the specified type from this . + + The type of annotations to remove. + + + + Removes the annotations of the specified type from this . + + The of annotations to remove. + + + + Compares tokens to determine whether they are equal. + + + + + Determines whether the specified objects are equal. + + The first object of type to compare. + The second object of type to compare. + + true if the specified objects are equal; otherwise, false. + + + + + Returns a hash code for the specified object. + + The for which a hash code is to be returned. + A hash code for the specified object. + The type of is a reference type and is null. + + + + Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. + + + + + Gets the at the reader's current position. + + + + + Initializes a new instance of the class. + + The token to read from. + + + + Reads the next JSON token from the stream. + + + true if the next token was read successfully; false if there are no more tokens to read. + + + + + Gets the path of the current JSON token. + + + + + Specifies the type of token. + + + + + No token type has been set. + + + + + A JSON object. + + + + + A JSON array. + + + + + A JSON constructor. + + + + + A JSON object property. + + + + + A comment. + + + + + An integer value. + + + + + A float value. + + + + + A string value. + + + + + A boolean value. + + + + + A null value. + + + + + An undefined value. + + + + + A date value. + + + + + A raw JSON value. + + + + + A collection of bytes value. + + + + + A Guid value. + + + + + A Uri value. + + + + + A TimeSpan value. + + + + + Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. + + + + + Gets the at the writer's current position. + + + + + Gets the token being writen. + + The token being writen. + + + + Initializes a new instance of the class writing to the given . + + The container being written to. + + + + Initializes a new instance of the class. + + + + + Flushes whatever is in the buffer to the underlying streams and also flushes the underlying stream. + + + + + Closes this stream and the underlying stream. + + + + + Writes the beginning of a JSON object. + + + + + Writes the beginning of a JSON array. + + + + + Writes the start of a constructor with the given name. + + The name of the constructor. + + + + Writes the end. + + The token. + + + + Writes the property name of a name/value pair on a JSON object. + + The name of the property. + + + + Writes a value. + An error will raised if the value cannot be written as a single JSON token. + + The value to write. + + + + Writes a null value. + + + + + Writes an undefined value. + + + + + Writes raw JSON. + + The raw JSON to write. + + + + Writes out a comment /*...*/ containing the specified text. + + Text to place inside the comment. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a [] value. + + The [] value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Writes a value. + + The value to write. + + + + Represents a value in JSON (string, integer, date, etc). + + + + + Initializes a new instance of the class from another object. + + A object to copy from. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Initializes a new instance of the class with the given value. + + The value. + + + + Gets a value indicating whether this token has child tokens. + + + true if this token has child values; otherwise, false. + + + + + Creates a comment with the given value. + + The value. + A comment with the given value. + + + + Creates a string with the given value. + + The value. + A string with the given value. + + + + Creates a null value. + + A null value. + + + + Creates a undefined value. + + A undefined value. + + + + Gets the node type for this . + + The type. + + + + Gets or sets the underlying token value. + + The underlying token value. + + + + Writes this token to a . + + A into which this method will write. + A collection of which will be used when writing the token. + + + + Indicates whether the current object is equal to another object of the same type. + + + true if the current object is equal to the parameter; otherwise, false. + + An object to compare with this object. + + + + Determines whether the specified is equal to the current . + + The to compare with the current . + + true if the specified is equal to the current ; otherwise, false. + + + The parameter is null. + + + + + Serves as a hash function for a particular type. + + + A hash code for the current . + + + + + Returns a that represents this instance. + + + A that represents this instance. + + + + + Returns a that represents this instance. + + The format. + + A that represents this instance. + + + + + Returns a that represents this instance. + + The format provider. + + A that represents this instance. + + + + + Returns a that represents this instance. + + The format. + The format provider. + + A that represents this instance. + + + + + Returns the responsible for binding operations performed on this object. + + The expression tree representation of the runtime value. + + The to bind this object. + + + + + Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + + An object to compare with this instance. + + A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: + Value + Meaning + Less than zero + This instance is less than . + Zero + This instance is equal to . + Greater than zero + This instance is greater than . + + + is not the same type as this instance. + + + + + Specifies the settings used when loading JSON. + + + + + Gets or sets how JSON comments are handled when loading JSON. + + The JSON comment handling. + + + + Gets or sets how JSON line info is handled when loading JSON. + + The JSON line info handling. + + + + Specifies how JSON arrays are merged together. + + + + Concatenate arrays. + + + Union arrays, skipping items that already exist. + + + Replace all array items. + + + Merge array items together, matched by index. + + + + Specifies how null value properties are merged. + + + + + The content's null value properties will be ignored during merging. + + + + + The content's null value properties will be merged. + + + + + Specifies the member serialization options for the . + + + + + All public members are serialized by default. Members can be excluded using or . + This is the default member serialization mode. + + + + + Only members marked with or are serialized. + This member serialization mode can also be set by marking the class with . + + + + + All public and private fields are serialized. Members can be excluded using or . + This member serialization mode can also be set by marking the class with + and setting IgnoreSerializableAttribute on to false. + + + + + Specifies metadata property handling options for the . + + + + + Read metadata properties located at the start of a JSON object. + + + + + Read metadata properties located anywhere in a JSON object. Note that this setting will impact performance. + + + + + Do not try to read metadata properties. + + + + + Specifies missing member handling options for the . + + + + + Ignore a missing member and do not attempt to deserialize it. + + + + + Throw a when a missing member is encountered during deserialization. + + + + + Specifies null value handling options for the . + + + + + + + + + Include null values when serializing and deserializing objects. + + + + + Ignore null values when serializing and deserializing objects. + + + + + Specifies how object creation is handled by the . + + + + + Reuse existing objects, create new objects when needed. + + + + + Only reuse existing objects. + + + + + Always create new objects. + + + + + Specifies reference handling options for the . + Note that references cannot be preserved when a value is set via a non-default constructor such as types that implement ISerializable. + + + + + + + + Do not preserve references when serializing types. + + + + + Preserve references when serializing into a JSON object structure. + + + + + Preserve references when serializing into a JSON array structure. + + + + + Preserve references when serializing. + + + + + Specifies reference loop handling options for the . + + + + + Throw a when a loop is encountered. + + + + + Ignore loop references and do not serialize. + + + + + Serialize loop references. + + + + + Indicating whether a property is required. + + + + + The property is not required. The default state. + + + + + The property must be defined in JSON but can be a null value. + + + + + The property must be defined in JSON and cannot be a null value. + + + + + The property is not required but it cannot be a null value. + + + + + + Contains the JSON schema extension methods. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + + Determines whether the is valid. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The source to test. + The schema to test with. + + true if the specified is valid; otherwise, false. + + + + + + Determines whether the is valid. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The source to test. + The schema to test with. + When this method returns, contains any error messages generated while validating. + + true if the specified is valid; otherwise, false. + + + + + + Validates the specified . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The source to test. + The schema to test with. + + + + + Validates the specified . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + The source to test. + The schema to test with. + The validation event handler. + + + + + An in-memory representation of a JSON Schema. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets or sets the id. + + + + + Gets or sets the title. + + + + + Gets or sets whether the object is required. + + + + + Gets or sets whether the object is read only. + + + + + Gets or sets whether the object is visible to users. + + + + + Gets or sets whether the object is transient. + + + + + Gets or sets the description of the object. + + + + + Gets or sets the types of values allowed by the object. + + The type. + + + + Gets or sets the pattern. + + The pattern. + + + + Gets or sets the minimum length. + + The minimum length. + + + + Gets or sets the maximum length. + + The maximum length. + + + + Gets or sets a number that the value should be divisble by. + + A number that the value should be divisble by. + + + + Gets or sets the minimum. + + The minimum. + + + + Gets or sets the maximum. + + The maximum. + + + + Gets or sets a flag indicating whether the value can not equal the number defined by the "minimum" attribute. + + A flag indicating whether the value can not equal the number defined by the "minimum" attribute. + + + + Gets or sets a flag indicating whether the value can not equal the number defined by the "maximum" attribute. + + A flag indicating whether the value can not equal the number defined by the "maximum" attribute. + + + + Gets or sets the minimum number of items. + + The minimum number of items. + + + + Gets or sets the maximum number of items. + + The maximum number of items. + + + + Gets or sets the of items. + + The of items. + + + + Gets or sets a value indicating whether items in an array are validated using the instance at their array position from . + + + true if items are validated using their array position; otherwise, false. + + + + + Gets or sets the of additional items. + + The of additional items. + + + + Gets or sets a value indicating whether additional items are allowed. + + + true if additional items are allowed; otherwise, false. + + + + + Gets or sets whether the array items must be unique. + + + + + Gets or sets the of properties. + + The of properties. + + + + Gets or sets the of additional properties. + + The of additional properties. + + + + Gets or sets the pattern properties. + + The pattern properties. + + + + Gets or sets a value indicating whether additional properties are allowed. + + + true if additional properties are allowed; otherwise, false. + + + + + Gets or sets the required property if this property is present. + + The required property if this property is present. + + + + Gets or sets the a collection of valid enum values allowed. + + A collection of valid enum values allowed. + + + + Gets or sets disallowed types. + + The disallow types. + + + + Gets or sets the default value. + + The default value. + + + + Gets or sets the collection of that this schema extends. + + The collection of that this schema extends. + + + + Gets or sets the format. + + The format. + + + + Initializes a new instance of the class. + + + + + Reads a from the specified . + + The containing the JSON Schema to read. + The object representing the JSON Schema. + + + + Reads a from the specified . + + The containing the JSON Schema to read. + The to use when resolving schema references. + The object representing the JSON Schema. + + + + Load a from a string that contains schema JSON. + + A that contains JSON. + A populated from the string that contains JSON. + + + + Parses the specified json. + + The json. + The resolver. + A populated from the string that contains JSON. + + + + Writes this schema to a . + + A into which this method will write. + + + + Writes this schema to a using the specified . + + A into which this method will write. + The resolver used. + + + + Returns a that represents the current . + + + A that represents the current . + + + + + + Returns detailed information about the schema exception. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets the line number indicating where the error occurred. + + The line number indicating where the error occurred. + + + + Gets the line position indicating where the error occurred. + + The line position indicating where the error occurred. + + + + Gets the path to the JSON where the error occurred. + + The path to the JSON where the error occurred. + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class + with a specified error message. + + The error message that explains the reason for the exception. + + + + Initializes a new instance of the class + with a specified error message and a reference to the inner exception that is the cause of this exception. + + The error message that explains the reason for the exception. + The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + + + + Initializes a new instance of the class. + + The that holds the serialized object data about the exception being thrown. + The that contains contextual information about the source or destination. + The parameter is null. + The class name is null or is zero (0). + + + + + Generates a from a specified . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets or sets how undefined schemas are handled by the serializer. + + + + + Gets or sets the contract resolver. + + The contract resolver. + + + + Generate a from the specified type. + + The type to generate a from. + A generated from the specified type. + + + + Generate a from the specified type. + + The type to generate a from. + The used to resolve schema references. + A generated from the specified type. + + + + Generate a from the specified type. + + The type to generate a from. + Specify whether the generated root will be nullable. + A generated from the specified type. + + + + Generate a from the specified type. + + The type to generate a from. + The used to resolve schema references. + Specify whether the generated root will be nullable. + A generated from the specified type. + + + + + Resolves from an id. + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets or sets the loaded schemas. + + The loaded schemas. + + + + Initializes a new instance of the class. + + + + + Gets a for the specified reference. + + The id. + A for the specified reference. + + + + + The value types allowed by the . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + No type specified. + + + + + String type. + + + + + Float type. + + + + + Integer type. + + + + + Boolean type. + + + + + Object type. + + + + + Array type. + + + + + Null type. + + + + + Any type. + + + + + + Specifies undefined schema Id handling options for the . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Do not infer a schema Id. + + + + + Use the .NET type name as the schema Id. + + + + + Use the assembly qualified .NET type name as the schema Id. + + + + + + Returns detailed information related to the . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Gets the associated with the validation error. + + The JsonSchemaException associated with the validation error. + + + + Gets the path of the JSON location where the validation error occurred. + + The path of the JSON location where the validation error occurred. + + + + Gets the text description corresponding to the validation error. + + The text description. + + + + + Represents the callback method that will handle JSON schema validation events and the . + + + JSON Schema validation has been moved to its own package. See http://www.newtonsoft.com/jsonschema for more details. + + + + + + Resolves member mappings for a type, camel casing property names. + + + + + Initializes a new instance of the class. + + + + + Resolves the name of the property. + + Name of the property. + The property name camel cased. + + + + Used by to resolves a for a given . + + + + + Gets a value indicating whether members are being get and set using dynamic code generation. + This value is determined by the runtime permissions available. + + + true if using dynamic code generation; otherwise, false. + + + + + Gets or sets the default members search flags. + + The default members search flags. + + + + Gets or sets a value indicating whether compiler generated members should be serialized. + + + true if serialized compiler generated members; otherwise, false. + + + + + Gets or sets a value indicating whether to ignore the interface when serializing and deserializing types. + + + true if the interface will be ignored when serializing and deserializing types; otherwise, false. + + + + + Gets or sets a value indicating whether to ignore the attribute when serializing and deserializing types. + + + true if the attribute will be ignored when serializing and deserializing types; otherwise, false. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + + If set to true the will use a cached shared with other resolvers of the same type. + Sharing the cache will significantly improve performance with multiple resolver instances because expensive reflection will only + happen once. This setting can cause unexpected behavior if different instances of the resolver are suppose to produce different + results. When set to false it is highly recommended to reuse instances with the . + + + + + Resolves the contract for a given type. + + The type to resolve a contract for. + The contract for a given type. + + + + Gets the serializable members for the type. + + The type to get serializable members for. + The serializable members for the type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates the constructor parameters. + + The constructor to create properties for. + The type's member properties. + Properties for the given . + + + + Creates a for the given . + + The matching member property. + The constructor parameter. + A created for the given . + + + + Resolves the default for the contract. + + Type of the object. + The contract's default . + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Creates a for the given type. + + Type of the object. + A for the given type. + + + + Determines which contract type is created for the given type. + + Type of the object. + A for the given type. + + + + Creates properties for the given . + + The type to create properties for. + /// The member serialization mode for the type. + Properties for the given . + + + + Creates the used by the serializer to get and set values from a member. + + The member. + The used by the serializer to get and set values from a member. + + + + Creates a for the given . + + The member's parent . + The member to create a for. + A created for the given . + + + + Resolves the name of the property. + + Name of the property. + Resolved name of the property. + + + + Resolves the key of the dictionary. By default is used to resolve dictionary keys. + + Key of the dictionary. + Resolved key of the dictionary. + + + + Gets the resolved name of the property. + + Name of the property. + Name of the property. + + + + The default serialization binder used when resolving and loading classes from type names. + + + + + When overridden in a derived class, controls the binding of a serialized object to a type. + + Specifies the name of the serialized object. + Specifies the name of the serialized object. + + The type of the object the formatter creates a new instance of. + + + + + When overridden in a derived class, controls the binding of a serialized object to a type. + + The type of the object the formatter creates a new instance of. + Specifies the name of the serialized object. + Specifies the name of the serialized object. + + + + Represents a trace writer that writes to the application's instances. + + + + + Gets the that will be used to filter the trace messages passed to the writer. + For example a filter level of Info will exclude Verbose messages and include Info, + Warning and Error messages. + + + The that will be used to filter the trace messages passed to the writer. + + + + + Writes the specified trace level, message and optional exception. + + The at which to write this trace. + The trace message. + The trace exception. This parameter is optional. + + + + Get and set values for a using dynamic methods. + + + + + Initializes a new instance of the class. + + The member info. + + + + Sets the value. + + The target to set the value on. + The value to set on the target. + + + + Gets the value. + + The target to get the value from. + The value. + + + + Provides information surrounding an error. + + + + + Gets the error. + + The error. + + + + Gets the original object that caused the error. + + The original object that caused the error. + + + + Gets the member that caused the error. + + The member that caused the error. + + + + Gets the path of the JSON location where the error occurred. + + The path of the JSON location where the error occurred. + + + + Gets or sets a value indicating whether this is handled. + + true if handled; otherwise, false. + + + + Provides data for the Error event. + + + + + Gets the current object the error event is being raised against. + + The current object the error event is being raised against. + + + + Gets the error context. + + The error context. + + + + Initializes a new instance of the class. + + The current object. + The error context. + + + + Get and set values for a using dynamic methods. + + + + + Initializes a new instance of the class. + + The member info. + + + + Sets the value. + + The target to set the value on. + The value to set on the target. + + + + Gets the value. + + The target to get the value from. + The value. + + + + Provides methods to get attributes. + + + + + Returns a collection of all of the attributes, or an empty collection if there are no attributes. + + When true, look up the hierarchy chain for the inherited custom attribute. + A collection of s, or an empty collection. + + + + Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. + + The type of the attributes. + When true, look up the hierarchy chain for the inherited custom attribute. + A collection of s, or an empty collection. + + + + Used by to resolves a for a given . + + + + + + + + + Resolves the contract for a given type. + + The type to resolve a contract for. + The contract for a given type. + + + + Used to resolve references when serializing and deserializing JSON by the . + + + + + Resolves a reference to its object. + + The serialization context. + The reference to resolve. + The object that + + + + Gets the reference for the sepecified object. + + The serialization context. + The object to get a reference for. + The reference to the object. + + + + Determines whether the specified object is referenced. + + The serialization context. + The object to test for a reference. + + true if the specified object is referenced; otherwise, false. + + + + + Adds a reference to the specified object. + + The serialization context. + The reference. + The object to reference. + + + + Represents a trace writer. + + + + + Gets the that will be used to filter the trace messages passed to the writer. + For example a filter level of Info will exclude Verbose messages and include Info, + Warning and Error messages. + + The that will be used to filter the trace messages passed to the writer. + + + + Writes the specified trace level, message and optional exception. + + The at which to write this trace. + The trace message. + The trace exception. This parameter is optional. + + + + Provides methods to get and set values. + + + + + Sets the value. + + The target to set the value on. + The value to set on the target. + + + + Gets the value. + + The target to get the value from. + The value. + + + + Contract details for a used by the . + + + + + Gets the of the collection items. + + The of the collection items. + + + + Gets a value indicating whether the collection type is a multidimensional array. + + true if the collection type is a multidimensional array; otherwise, false. + + + + Gets or sets the function used to create the object. When set this function will override . + + The function used to create the object. + + + + Gets a value indicating whether the creator has a parameter with the collection values. + + true if the creator has a parameter with the collection values; otherwise, false. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Gets or sets the default collection items . + + The converter. + + + + Gets or sets a value indicating whether the collection items preserve object references. + + true if collection items preserve object references; otherwise, false. + + + + Gets or sets the collection item reference loop handling. + + The reference loop handling. + + + + Gets or sets the collection item type name handling. + + The type name handling. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Handles serialization callback events. + + The object that raised the callback event. + The streaming context. + + + + Handles serialization error callback events. + + The object that raised the callback event. + The streaming context. + The error context. + + + + Sets extension data for an object during deserialization. + + The object to set extension data on. + The extension data key. + The extension data value. + + + + Gets extension data for an object during serialization. + + The object to set extension data on. + + + + Contract details for a used by the . + + + + + Gets the underlying type for the contract. + + The underlying type for the contract. + + + + Gets or sets the type created during deserialization. + + The type created during deserialization. + + + + Gets or sets whether this type contract is serialized as a reference. + + Whether this type contract is serialized as a reference. + + + + Gets or sets the default for this contract. + + The converter. + + + + Gets or sets all methods called immediately after deserialization of the object. + + The methods called immediately after deserialization of the object. + + + + Gets or sets all methods called during deserialization of the object. + + The methods called during deserialization of the object. + + + + Gets or sets all methods called after serialization of the object graph. + + The methods called after serialization of the object graph. + + + + Gets or sets all methods called before serialization of the object. + + The methods called before serialization of the object. + + + + Gets or sets all method called when an error is thrown during the serialization of the object. + + The methods called when an error is thrown during the serialization of the object. + + + + Gets or sets the method called immediately after deserialization of the object. + + The method called immediately after deserialization of the object. + + + + Gets or sets the method called during deserialization of the object. + + The method called during deserialization of the object. + + + + Gets or sets the method called after serialization of the object graph. + + The method called after serialization of the object graph. + + + + Gets or sets the method called before serialization of the object. + + The method called before serialization of the object. + + + + Gets or sets the method called when an error is thrown during the serialization of the object. + + The method called when an error is thrown during the serialization of the object. + + + + Gets or sets the default creator method used to create the object. + + The default creator method used to create the object. + + + + Gets or sets a value indicating whether the default creator is non public. + + true if the default object creator is non-public; otherwise, false. + + + + Contract details for a used by the . + + + + + Gets or sets the property name resolver. + + The property name resolver. + + + + Gets or sets the dictionary key resolver. + + The dictionary key resolver. + + + + Gets the of the dictionary keys. + + The of the dictionary keys. + + + + Gets the of the dictionary values. + + The of the dictionary values. + + + + Gets or sets the function used to create the object. When set this function will override . + + The function used to create the object. + + + + Gets a value indicating whether the creator has a parameter with the dictionary values. + + true if the creator has a parameter with the dictionary values; otherwise, false. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Gets the object's properties. + + The object's properties. + + + + Gets or sets the property name resolver. + + The property name resolver. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Gets or sets the ISerializable object constructor. + + The ISerializable object constructor. + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Gets or sets the object member serialization. + + The member object serialization. + + + + Gets or sets a value that indicates whether the object's properties are required. + + + A value indicating whether the object's properties are required. + + + + + Gets the object's properties. + + The object's properties. + + + + Gets the constructor parameters required for any non-default constructor + + + + + Gets a collection of instances that define the parameters used with . + + + + + Gets or sets the override constructor used to create the object. + This is set when a constructor is marked up using the + JsonConstructor attribute. + + The override constructor. + + + + Gets or sets the parametrized constructor used to create the object. + + The parametrized constructor. + + + + Gets or sets the function used to create the object. When set this function will override . + This function is called with a collection of arguments which are defined by the collection. + + The function used to create the object. + + + + Gets or sets the extension data setter. + + + + + Gets or sets the extension data getter. + + + + + Gets or sets the extension data value type. + + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Contract details for a used by the . + + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Maps a JSON property to a .NET member or constructor parameter. + + + + + Gets or sets the name of the property. + + The name of the property. + + + + Gets or sets the type that declared this property. + + The type that declared this property. + + + + Gets or sets the order of serialization of a member. + + The numeric order of serialization. + + + + Gets or sets the name of the underlying member or parameter. + + The name of the underlying member or parameter. + + + + Gets the that will get and set the during serialization. + + The that will get and set the during serialization. + + + + Gets or sets the for this property. + + The for this property. + + + + Gets or sets the type of the property. + + The type of the property. + + + + Gets or sets the for the property. + If set this converter takes presidence over the contract converter for the property type. + + The converter. + + + + Gets or sets the member converter. + + The member converter. + + + + Gets or sets a value indicating whether this is ignored. + + true if ignored; otherwise, false. + + + + Gets or sets a value indicating whether this is readable. + + true if readable; otherwise, false. + + + + Gets or sets a value indicating whether this is writable. + + true if writable; otherwise, false. + + + + Gets or sets a value indicating whether this has a member attribute. + + true if has a member attribute; otherwise, false. + + + + Gets the default value. + + The default value. + + + + Gets or sets a value indicating whether this is required. + + A value indicating whether this is required. + + + + Gets or sets a value indicating whether this property preserves object references. + + + true if this instance is reference; otherwise, false. + + + + + Gets or sets the property null value handling. + + The null value handling. + + + + Gets or sets the property default value handling. + + The default value handling. + + + + Gets or sets the property reference loop handling. + + The reference loop handling. + + + + Gets or sets the property object creation handling. + + The object creation handling. + + + + Gets or sets or sets the type name handling. + + The type name handling. + + + + Gets or sets a predicate used to determine whether the property should be serialize. + + A predicate used to determine whether the property should be serialize. + + + + Gets or sets a predicate used to determine whether the property should be deserialized. + + A predicate used to determine whether the property should be deserialized. + + + + Gets or sets a predicate used to determine whether the property should be serialized. + + A predicate used to determine whether the property should be serialized. + + + + Gets or sets an action used to set whether the property has been deserialized. + + An action used to set whether the property has been deserialized. + + + + Returns a that represents this instance. + + + A that represents this instance. + + + + + Gets or sets the converter used when serializing the property's collection items. + + The collection's items converter. + + + + Gets or sets whether this property's collection items are serialized as a reference. + + Whether this property's collection items are serialized as a reference. + + + + Gets or sets the the type name handling used when serializing the property's collection items. + + The collection's items type name handling. + + + + Gets or sets the the reference loop handling used when serializing the property's collection items. + + The collection's items reference loop handling. + + + + A collection of objects. + + + + + Initializes a new instance of the class. + + The type. + + + + When implemented in a derived class, extracts the key from the specified element. + + The element from which to extract the key. + The key for the specified element. + + + + Adds a object. + + The property to add to the collection. + + + + Gets the closest matching object. + First attempts to get an exact case match of propertyName and then + a case insensitive match. + + Name of the property. + A matching property if found. + + + + Gets a property by property name. + + The name of the property to get. + Type property name string comparison. + A matching property if found. + + + + Contract details for a used by the . + + + + + Initializes a new instance of the class. + + The underlying type for the contract. + + + + Lookup and create an instance of the JsonConverter type described by the argument. + + The JsonConverter type to create. + Optional arguments to pass to an initializing constructor of the JsonConverter. + If null, the default constructor is used. + + + + Create a factory function that can be used to create instances of a JsonConverter described by the + argument type. The returned function can then be used to either invoke the converter's default ctor, or any + parameterized constructors by way of an object array. + + + + + Represents a trace writer that writes to memory. When the trace message limit is + reached then old trace messages will be removed as new messages are added. + + + + + Gets the that will be used to filter the trace messages passed to the writer. + For example a filter level of Info will exclude Verbose messages and include Info, + Warning and Error messages. + + + The that will be used to filter the trace messages passed to the writer. + + + + + Initializes a new instance of the class. + + + + + Writes the specified trace level, message and optional exception. + + The at which to write this trace. + The trace message. + The trace exception. This parameter is optional. + + + + Returns an enumeration of the most recent trace messages. + + An enumeration of the most recent trace messages. + + + + Returns a of the most recent trace messages. + + + A of the most recent trace messages. + + + + + Represents a method that constructs an object. + + The object type to create. + + + + When applied to a method, specifies that the method is called when an error occurs serializing an object. + + + + + Provides methods to get attributes from a , , or . + + + + + Initializes a new instance of the class. + + The instance to get attributes for. This parameter should be a , , or . + + + + Returns a collection of all of the attributes, or an empty collection if there are no attributes. + + When true, look up the hierarchy chain for the inherited custom attribute. + A collection of s, or an empty collection. + + + + Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. + + The type of the attributes. + When true, look up the hierarchy chain for the inherited custom attribute. + A collection of s, or an empty collection. + + + + Get and set values for a using reflection. + + + + + Initializes a new instance of the class. + + The member info. + + + + Sets the value. + + The target to set the value on. + The value to set on the target. + + + + Gets the value. + + The target to get the value from. + The value. + + + + Specifies how strings are escaped when writing JSON text. + + + + + Only control characters (e.g. newline) are escaped. + + + + + All non-ASCII and control characters (e.g. newline) are escaped. + + + + + HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped. + + + + + Specifies type name handling options for the . + + + should be used with caution when your application deserializes JSON from an external source. + Incoming types should be validated with a custom + when deserializing with a value other than TypeNameHandling.None. + + + + + Do not include the .NET type name when serializing types. + + + + + Include the .NET type name when serializing into a JSON object structure. + + + + + Include the .NET type name when serializing into a JSON array structure. + + + + + Always include the .NET type name when serializing. + + + + + Include the .NET type name when the type of the object being serialized is not the same as its declared type. + + + + + Determines whether the collection is null or empty. + + The collection. + + true if the collection is null or empty; otherwise, false. + + + + + Adds the elements of the specified collection to the specified generic IList. + + The list to add to. + The collection of elements to add. + + + + Converts the value to the specified type. If the value is unable to be converted, the + value is checked whether it assignable to the specified type. + + The value to convert. + The culture to use when converting. + The type to convert or cast the value to. + + The converted type. If conversion was unsuccessful, the initial value + is returned if assignable to the target type. + + + + + Helper method for generating a MetaObject which calls a + specific method on Dynamic that returns a result + + + + + Helper method for generating a MetaObject which calls a + specific method on Dynamic, but uses one of the arguments for + the result. + + + + + Helper method for generating a MetaObject which calls a + specific method on Dynamic, but uses one of the arguments for + the result. + + + + + Returns a Restrictions object which includes our current restrictions merged + with a restriction limiting our type + + + + + Gets a dictionary of the names and values of an Enum type. + + + + + + Gets a dictionary of the names and values of an Enum type. + + The enum type to get names and values for. + + + + + Gets the type of the typed collection's items. + + The type. + The type of the typed collection's items. + + + + Gets the member's underlying type. + + The member. + The underlying type of the member. + + + + Determines whether the member is an indexed property. + + The member. + + true if the member is an indexed property; otherwise, false. + + + + + Determines whether the property is an indexed property. + + The property. + + true if the property is an indexed property; otherwise, false. + + + + + Gets the member's value on the object. + + The member. + The target object. + The member's value on the object. + + + + Sets the member's value on the target object. + + The member. + The target. + The value. + + + + Determines whether the specified MemberInfo can be read. + + The MemberInfo to determine whether can be read. + /// if set to true then allow the member to be gotten non-publicly. + + true if the specified MemberInfo can be read; otherwise, false. + + + + + Determines whether the specified MemberInfo can be set. + + The MemberInfo to determine whether can be set. + if set to true then allow the member to be set non-publicly. + if set to true then allow the member to be set if read-only. + + true if the specified MemberInfo can be set; otherwise, false. + + + + + Builds a string. Unlike StringBuilder this class lets you reuse it's internal buffer. + + + + + Determines whether the string is all white space. Empty string will return false. + + The string to test whether it is all white space. + + true if the string is all white space; otherwise, false. + + + + + Nulls an empty string. + + The string. + Null if the string was null, otherwise the string unchanged. + + + + Specifies the state of the . + + + + + An exception has been thrown, which has left the in an invalid state. + You may call the method to put the in the Closed state. + Any other method calls results in an being thrown. + + + + + The method has been called. + + + + + An object is being written. + + + + + A array is being written. + + + + + A constructor is being written. + + + + + A property is being written. + + + + + A write method has not been called. + + + + diff --git a/WizBot/bin/Debug/RestSharp.xml b/WizBot/bin/Debug/RestSharp.xml new file mode 100644 index 000000000..16ca278fa --- /dev/null +++ b/WizBot/bin/Debug/RestSharp.xml @@ -0,0 +1,3095 @@ + + + + RestSharp + + + + + JSON WEB TOKEN (JWT) Authenticator class. + https://tools.ietf.org/html/draft-ietf-oauth-json-web-token + + + + + Tries to Authenticate with the credentials of the currently logged in user, or impersonate a user + + + + + Authenticate with the credentials of the currently logged in user + + + + + Authenticate by impersonation + + + + + + + Authenticate by impersonation, using an existing ICredentials instance + + + + + + + + + Base class for OAuth 2 Authenticators. + + + Since there are many ways to authenticate in OAuth2, + this is used as a base class to differentiate between + other authenticators. + + Any other OAuth2 authenticators must derive from this + abstract class. + + + + + Access token to be used when authenticating. + + + + + Initializes a new instance of the class. + + + The access token. + + + + + Gets the access token. + + + + + The OAuth 2 authenticator using URI query parameter. + + + Based on http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.2 + + + + + Initializes a new instance of the class. + + + The access token. + + + + + The OAuth 2 authenticator using the authorization request header field. + + + Based on http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-5.1.1 + + + + + Stores the Authorization header value as "[tokenType] accessToken". used for performance. + + + + + Initializes a new instance of the class. + + + The access token. + + + + + Initializes a new instance of the class. + + + The access token. + + + The token type. + + + + + All text parameters are UTF-8 encoded (per section 5.1). + + + + + + Generates a random 16-byte lowercase alphanumeric string. + + + + + + + Generates a timestamp based on the current elapsed seconds since '01/01/1970 0000 GMT" + + + + + + + Generates a timestamp based on the elapsed seconds of a given time since '01/01/1970 0000 GMT" + + + A specified point in time. + + + + + The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986. + + + + + + URL encodes a string based on section 5.1 of the OAuth spec. + Namely, percent encoding with [RFC3986], avoiding unreserved characters, + upper-casing hexadecimal characters, and UTF-8 encoding for text value pairs. + + The value to escape. + The escaped value. + + The method is supposed to take on + RFC 3986 behavior if certain elements are present in a .config file. Even if this + actually worked (which in my experiments it doesn't), we can't rely on every + host actually having this configuration element present. + + + + + + + URL encodes a string based on section 5.1 of the OAuth spec. + Namely, percent encoding with [RFC3986], avoiding unreserved characters, + upper-casing hexadecimal characters, and UTF-8 encoding for text value pairs. + + + + + + + Sorts a collection of key-value pairs by name, and then value if equal, + concatenating them into a single string. This string should be encoded + prior to, or after normalization is run. + + + + + + + + Sorts a by name, and then value if equal. + + A collection of parameters to sort + A sorted parameter collection + + + + Creates a request URL suitable for making OAuth requests. + Resulting URLs must exclude port 80 or port 443 when accompanied by HTTP and HTTPS, respectively. + Resulting URLs must be lower case. + + + The original request URL + + + + + Creates a request elements concatentation value to send with a request. + This is also known as the signature base. + + + + The request's HTTP method type + The request URL + The request's parameters + A signature base string + + + + Creates a signature value given a signature base and the consumer secret. + This method is used when the token secret is currently unknown. + + + The hashing method + The signature base + The consumer key + + + + + Creates a signature value given a signature base and the consumer secret. + This method is used when the token secret is currently unknown. + + + The hashing method + The treatment to use on a signature value + The signature base + The consumer key + + + + + Creates a signature value given a signature base and the consumer secret and a known token secret. + + + The hashing method + The signature base + The consumer secret + The token secret + + + + + Creates a signature value given a signature base and the consumer secret and a known token secret. + + + The hashing method + The treatment to use on a signature value + The signature base + The consumer secret + The token secret + + + + + A class to encapsulate OAuth authentication flow. + + + + + + Generates a instance to pass to an + for the purpose of requesting an + unauthorized request token. + + The HTTP method for the intended request + + + + + + Generates a instance to pass to an + for the purpose of requesting an + unauthorized request token. + + The HTTP method for the intended request + Any existing, non-OAuth query parameters desired in the request + + + + + + Generates a instance to pass to an + for the purpose of exchanging a request token + for an access token authorized by the user at the Service Provider site. + + The HTTP method for the intended request + + + + + Generates a instance to pass to an + for the purpose of exchanging a request token + for an access token authorized by the user at the Service Provider site. + + The HTTP method for the intended request + + Any existing, non-OAuth query parameters desired in the request + + + + Generates a instance to pass to an + for the purpose of exchanging user credentials + for an access token authorized by the user at the Service Provider site. + + The HTTP method for the intended request + + Any existing, non-OAuth query parameters desired in the request + + + + + + + + + + + + + Allows control how class and property names and values are deserialized by XmlAttributeDeserializer + + + + + The name to use for the serialized element + + + + + Sets if the property to Deserialize is an Attribute or Element (Default: false) + + + + + Wrapper for System.Xml.Serialization.XmlSerializer. + + + + + Types of parameters that can be added to requests + + + + + Data formats + + + + + HTTP method to use when making requests + + + + + Format strings for commonly-used date formats + + + + + .NET format string for ISO 8601 date format + + + + + .NET format string for roundtrip date format + + + + + Status for responses (surprised?) + + + + + Extension method overload! + + + + + Save a byte array to a file + + Bytes to save + Full path to save file to + + + + Read a stream into a byte array + + Stream to read + byte[] + + + + Copies bytes from one stream to another + + The input stream. + The output stream. + + + + Converts a byte array to a string, using its byte order mark to convert it to the right encoding. + http://www.shrinkrays.net/code-snippets/csharp/an-extension-method-for-converting-a-byte-array-to-a-string.aspx + + An array of bytes to convert + The byte as a string. + + + + Decodes an HTML-encoded string and returns the decoded string. + + The HTML string to decode. + The decoded text. + + + + Decodes an HTML-encoded string and sends the resulting output to a TextWriter output stream. + + The HTML string to decode + The TextWriter output stream containing the decoded string. + + + + HTML-encodes a string and sends the resulting output to a TextWriter output stream. + + The string to encode. + The TextWriter output stream containing the encoded string. + + + + Reflection extensions + + + + + Retrieve an attribute from a member (property) + + Type of attribute to retrieve + Member to retrieve attribute from + + + + + Retrieve an attribute from a type + + Type of attribute to retrieve + Type to retrieve attribute from + + + + + Checks a type to see if it derives from a raw generic (e.g. List[[]]) + + + + + + + + Find a value from a System.Enum by trying several possible variants + of the string value of the enum. + + Type of enum + Value for which to search + The culture used to calculate the name variants + + + + + Convert a to a instance. + + The response status. + + responseStatus + + + + Uses Uri.EscapeDataString() based on recommendations on MSDN + http://blogs.msdn.com/b/yangxind/archive/2006/11/09/don-t-use-net-system-uri-unescapedatastring-in-url-decoding.aspx + + + + + Check that a string is not null or empty + + String to check + bool + + + + Remove underscores from a string + + String to process + string + + + + Parses most common JSON date formats + + JSON value to parse + + DateTime + + + + Remove leading and trailing " from a string + + String to parse + String + + + + Checks a string to see if it matches a regex + + String to check + Pattern to match + bool + + + + Converts a string to pascal case + + String to convert + + string + + + + Converts a string to pascal case with the option to remove underscores + + String to convert + Option to remove underscores + + + + + + Converts a string to camel case + + String to convert + + String + + + + Convert the first letter of a string to lower case + + String to convert + string + + + + Checks to see if a string is all uppper case + + String to check + bool + + + + Add underscores to a pascal-cased string + + String to convert + string + + + + Add dashes to a pascal-cased string + + String to convert + string + + + + Add an undescore prefix to a pascasl-cased string + + + + + + + Add spaces to a pascal-cased string + + String to convert + string + + + + Return possible variants of a name for name matching. + + String to convert + The culture to use for conversion + IEnumerable<string> + + + + XML Extension Methods + + + + + Returns the name of an element with the namespace if specified + + Element name + XML Namespace + + + + + Container for files to be uploaded with requests + + + + + Creates a file parameter from an array of bytes. + + The parameter name to use in the request. + The data to use as the file's contents. + The filename to use in the request. + The content type to use in the request. + The + + + + Creates a file parameter from an array of bytes. + + The parameter name to use in the request. + The data to use as the file's contents. + The filename to use in the request. + The using the default content type. + + + + The length of data to be sent + + + + + Provides raw data for file + + + + + Name of the file to use when uploading + + + + + MIME content type of file + + + + + Name of the parameter + + + + + HttpWebRequest wrapper (async methods) + + + HttpWebRequest wrapper + + + HttpWebRequest wrapper (sync methods) + + + + + Always send a multipart/form-data request - even when no Files are present. + + + + + An alternative to RequestBody, for when the caller already has the byte array. + + + + + Execute an async POST-style request with the specified HTTP Method. + + + The HTTP method to execute. + + + + + Execute an async GET-style request with the specified HTTP Method. + + + The HTTP method to execute. + + + + + Creates an IHttp + + + + + + Default constructor + + + + + Execute a POST request + + + + + Execute a PUT request + + + + + Execute a GET request + + + + + Execute a HEAD request + + + + + Execute an OPTIONS request + + + + + Execute a DELETE request + + + + + Execute a PATCH request + + + + + Execute a MERGE request + + + + + Execute a GET-style request with the specified HTTP Method. + + The HTTP method to execute. + + + + + Execute a POST-style request with the specified HTTP Method. + + The HTTP method to execute. + + + + + True if this HTTP request has any HTTP parameters + + + + + True if this HTTP request has any HTTP cookies + + + + + True if a request body has been specified + + + + + True if files have been set to be uploaded + + + + + Always send a multipart/form-data request - even when no Files are present. + + + + + UserAgent to be sent with request + + + + + Timeout in milliseconds to be used for the request + + + + + The number of milliseconds before the writing or reading times out. + + + + + System.Net.ICredentials to be sent with request + + + + + The System.Net.CookieContainer to be used for the request + + + + + The method to use to write the response instead of reading into RawBytes + + + + + Collection of files to be sent with request + + + + + Whether or not HTTP 3xx response redirects should be automatically followed + + + + + X509CertificateCollection to be sent with request + + + + + Maximum number of automatic redirects to follow if FollowRedirects is true + + + + + Determine whether or not the "default credentials" (e.g. the user account under which the current process is running) + will be sent along to the server. + + + + + HTTP headers to be sent with request + + + + + HTTP parameters (QueryString or Form values) to be sent with request + + + + + HTTP cookies to be sent with request + + + + + Request body to be sent with request + + + + + Content type of the request body. + + + + + An alternative to RequestBody, for when the caller already has the byte array. + + + + + URL to call for this request + + + + + Flag to send authorisation header with the HttpWebRequest + + + + + Proxy info to be sent with request + + + + + Caching policy for requests created with this wrapper. + + + + + Representation of an HTTP cookie + + + + + Comment of the cookie + + + + + Comment of the cookie + + + + + Indicates whether the cookie should be discarded at the end of the session + + + + + Domain of the cookie + + + + + Indicates whether the cookie is expired + + + + + Date and time that the cookie expires + + + + + Indicates that this cookie should only be accessed by the server + + + + + Name of the cookie + + + + + Path of the cookie + + + + + Port of the cookie + + + + + Indicates that the cookie should only be sent over secure channels + + + + + Date and time the cookie was created + + + + + Value of the cookie + + + + + Version of the cookie + + + + + Container for HTTP file + + + + + The length of data to be sent + + + + + Provides raw data for file + + + + + Name of the file to use when uploading + + + + + MIME content type of file + + + + + Name of the parameter + + + + + Representation of an HTTP header + + + + + Name of the header + + + + + Value of the header + + + + + Representation of an HTTP parameter (QueryString or Form value) + + + + + Name of the parameter + + + + + Value of the parameter + + + + + Content-Type of the parameter + + + + + HTTP response data + + + + + HTTP response data + + + + + MIME content type of response + + + + + Length in bytes of the response content + + + + + Encoding of the response content + + + + + String representation of response content + + + + + HTTP response status code + + + + + Description of HTTP status returned + + + + + Response content + + + + + The URL that actually responded to the content (different from request if redirected) + + + + + HttpWebResponse.Server + + + + + Headers returned by server with the response + + + + + Cookies returned by server with the response + + + + + Status of the request. Will return Error for transport errors. + HTTP errors will still return ResponseStatus.Completed, check StatusCode instead + + + + + Transport or other non-HTTP error generated while attempting request + + + + + Exception thrown when error is encountered. + + + + + Default constructor + + + + + MIME content type of response + + + + + Length in bytes of the response content + + + + + Encoding of the response content + + + + + Lazy-loaded string representation of response content + + + + + HTTP response status code + + + + + Description of HTTP status returned + + + + + Response content + + + + + The URL that actually responded to the content (different from request if redirected) + + + + + HttpWebResponse.Server + + + + + Headers returned by server with the response + + + + + Cookies returned by server with the response + + + + + Status of the request. Will return Error for transport errors. + HTTP errors will still return ResponseStatus.Completed, check StatusCode instead + + + + + Transport or other non-HTTP error generated while attempting request + + + + + Exception thrown when error is encountered. + + + + + Executes a GET-style request and callback asynchronously, authenticating if needed + + Request to be executed + Callback function to be executed upon completion providing access to the async handle. + The HTTP method to execute + + + + Executes a POST-style request and callback asynchronously, authenticating if needed + + Request to be executed + Callback function to be executed upon completion providing access to the async handle. + The HTTP method to execute + + + + Executes a GET-style request and callback asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + Callback function to be executed upon completion + The HTTP method to execute + + + + Executes a GET-style request and callback asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + Callback function to be executed upon completion + The HTTP method to execute + + + + Executes the request and callback asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + The cancellation token + + + + Executes the request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + + + + Executes a GET-style request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + + + + Executes a GET-style request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + The cancellation token + + + + Executes a POST-style request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + + + + Executes a POST-style request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + The cancellation token + + + + Executes the request and callback asynchronously, authenticating if needed + + Request to be executed + The cancellation token + + + + Executes the request asynchronously, authenticating if needed + + Request to be executed + + + + Executes a GET-style asynchronously, authenticating if needed + + Request to be executed + + + + Executes a GET-style asynchronously, authenticating if needed + + Request to be executed + The cancellation token + + + + Executes a POST-style asynchronously, authenticating if needed + + Request to be executed + + + + Executes a POST-style asynchronously, authenticating if needed + + Request to be executed + The cancellation token + + + + X509CertificateCollection to be sent with request + + + + + Adds a file to the Files collection to be included with a POST or PUT request + (other methods do not support file uploads). + + The parameter name to use in the request + Full path to file to upload + The MIME type of the file to upload + This request + + + + Adds the bytes to the Files collection with the specified file name and content type + + The parameter name to use in the request + The file data + The file name to use for the uploaded file + The MIME type of the file to upload + This request + + + + Adds the bytes to the Files collection with the specified file name and content type + + The parameter name to use in the request + A function that writes directly to the stream. Should NOT close the stream. + The file name to use for the uploaded file + The MIME type of the file to upload + This request + + + + Add bytes to the Files collection as if it was a file of specific type + + A form parameter name + The file data + The file name to use for the uploaded file + Specific content type. Es: application/x-gzip + + + + + Serializes obj to format specified by RequestFormat, but passes xmlNamespace if using the default XmlSerializer + The default format is XML. Change RequestFormat if you wish to use a different serialization format. + + The object to serialize + The XML namespace to use when serializing + This request + + + + Serializes obj to data format specified by RequestFormat and adds it to the request body. + The default format is XML. Change RequestFormat if you wish to use a different serialization format. + + The object to serialize + This request + + + + Serializes obj to JSON format and adds it to the request body. + + The object to serialize + This request + + + + Serializes obj to XML format and adds it to the request body. + + The object to serialize + This request + + + + Serializes obj to format specified by RequestFormat, but passes xmlNamespace if using the default XmlSerializer + Serializes obj to XML format and passes xmlNamespace then adds it to the request body. + + The object to serialize + The XML namespace to use when serializing + This request + + + + Calls AddParameter() for all public, readable properties specified in the includedProperties list + + + request.AddObject(product, "ProductId", "Price", ...); + + The object with properties to add as parameters + The names of the properties to include + This request + + + + Calls AddParameter() for all public, readable properties of obj + + The object with properties to add as parameters + This request + + + + Add the parameter to the request + + Parameter to add + + + + + Adds a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT) + + Name of the parameter + Value of the parameter + This request + + + + Adds a parameter to the request. There are five types of parameters: + - GetOrPost: Either a QueryString value or encoded form value based on method + - HttpHeader: Adds the name/value pair to the HTTP request's Headers collection + - UrlSegment: Inserted into URL if there is a matching url token e.g. {AccountId} + - Cookie: Adds the name/value pair to the HTTP request's Cookies collection + - RequestBody: Used by AddBody() (not recommended to use directly) + + Name of the parameter + Value of the parameter + The type of parameter to add + This request + + + + Adds a parameter to the request. There are five types of parameters: + - GetOrPost: Either a QueryString value or encoded form value based on method + - HttpHeader: Adds the name/value pair to the HTTP request's Headers collection + - UrlSegment: Inserted into URL if there is a matching url token e.g. {AccountId} + - Cookie: Adds the name/value pair to the HTTP request's Cookies collection + - RequestBody: Used by AddBody() (not recommended to use directly) + + Name of the parameter + Value of the parameter + Content-Type of the parameter + The type of parameter to add + This request + + + + Shortcut to AddParameter(name, value, HttpHeader) overload + + Name of the header to add + Value of the header to add + + + + + Shortcut to AddParameter(name, value, Cookie) overload + + Name of the cookie to add + Value of the cookie to add + + + + + Shortcut to AddParameter(name, value, UrlSegment) overload + + Name of the segment to add + Value of the segment to add + + + + + Shortcut to AddParameter(name, value, QueryString) overload + + Name of the parameter to add + Value of the parameter to add + + + + + Always send a multipart/form-data request - even when no Files are present. + + + + + Serializer to use when writing JSON request bodies. Used if RequestFormat is Json. + By default the included JsonSerializer is used (currently using JSON.NET default serialization). + + + + + Serializer to use when writing XML request bodies. Used if RequestFormat is Xml. + By default the included XmlSerializer is used. + + + + + Set this to write response to Stream rather than reading into memory. + + + + + Container of all HTTP parameters to be passed with the request. + See AddParameter() for explanation of the types of parameters that can be passed + + + + + Container of all the files to be uploaded with the request. + + + + + Determines what HTTP method to use for this request. Supported methods: GET, POST, PUT, DELETE, HEAD, OPTIONS + Default is GET + + + + + The Resource URL to make the request against. + Tokens are substituted with UrlSegment parameters and match by name. + Should not include the scheme or domain. Do not include leading slash. + Combined with RestClient.BaseUrl to assemble final URL: + {BaseUrl}/{Resource} (BaseUrl is scheme + domain, e.g. http://example.com) + + + // example for url token replacement + request.Resource = "Products/{ProductId}"; + request.AddParameter("ProductId", 123, ParameterType.UrlSegment); + + + + + Serializer to use when writing XML request bodies. Used if RequestFormat is Xml. + By default XmlSerializer is used. + + + + + Used by the default deserializers to determine where to start deserializing from. + Can be used to skip container or root elements that do not have corresponding deserialzation targets. + + + + + Used by the default deserializers to explicitly set which date format string to use when parsing dates. + + + + + Used by XmlDeserializer. If not specified, XmlDeserializer will flatten response by removing namespaces from element names. + + + + + In general you would not need to set this directly. Used by the NtlmAuthenticator. + + + + + Timeout in milliseconds to be used for the request. This timeout value overrides a timeout set on the RestClient. + + + + + The number of milliseconds before the writing or reading times out. This timeout value overrides a timeout set on the RestClient. + + + + + How many attempts were made to send this Request? + + + This Number is incremented each time the RestClient sends the request. + Useful when using Asynchronous Execution with Callbacks + + + + + Determine whether or not the "default credentials" (e.g. the user account under which the current process is running) + will be sent along to the server. The default is false. + + + + + Container for data sent back from API + + + + + The RestRequest that was made to get this RestResponse + + + Mainly for debugging if ResponseStatus is not OK + + + + + MIME content type of response + + + + + Length in bytes of the response content + + + + + Encoding of the response content + + + + + String representation of response content + + + + + HTTP response status code + + + + + Description of HTTP status returned + + + + + Response content + + + + + The URL that actually responded to the content (different from request if redirected) + + + + + HttpWebResponse.Server + + + + + Cookies returned by server with the response + + + + + Headers returned by server with the response + + + + + Status of the request. Will return Error for transport errors. + HTTP errors will still return ResponseStatus.Completed, check StatusCode instead + + + + + Transport or other non-HTTP error generated while attempting request + + + + + Exceptions thrown during the request, if any. + + Will contain only network transport or framework exceptions thrown during the request. + HTTP protocol errors are handled by RestSharp and will not appear here. + + + + Container for data sent back from API including deserialized data + + Type of data to deserialize to + + + + Deserialized entity data + + + + + Parameter container for REST requests + + + + + Return a human-readable representation of this parameter + + String + + + + Name of the parameter + + + + + Value of the parameter + + + + + Type of the parameter + + + + + MIME content type of the parameter + + + + + Client to translate RestRequests into Http requests and process response result + + + + + Executes the request and callback asynchronously, authenticating if needed + + Request to be executed + Callback function to be executed upon completion providing access to the async handle. + + + + Executes a GET-style request and callback asynchronously, authenticating if needed + + Request to be executed + Callback function to be executed upon completion providing access to the async handle. + The HTTP method to execute + + + + Executes a POST-style request and callback asynchronously, authenticating if needed + + Request to be executed + Callback function to be executed upon completion providing access to the async handle. + The HTTP method to execute + + + + Executes the request and callback asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + Callback function to be executed upon completion + + + + Executes a GET-style request and callback asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + Callback function to be executed upon completion + The HTTP method to execute + + + + Executes a POST-style request and callback asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + Callback function to be executed upon completion + The HTTP method to execute + + + + Executes a GET-style request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + + + + Executes a GET-style request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + The cancellation token + + + + Executes a POST-style request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + + + + Executes a POST-style request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + The cancellation token + + + + Executes the request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + + + + Executes the request asynchronously, authenticating if needed + + Target deserialization type + Request to be executed + The cancellation token + + + + Executes the request asynchronously, authenticating if needed + + Request to be executed + + + + Executes a GET-style asynchronously, authenticating if needed + + Request to be executed + + + + Executes a GET-style asynchronously, authenticating if needed + + Request to be executed + The cancellation token + + + + Executes a POST-style asynchronously, authenticating if needed + + Request to be executed + + + + Executes a POST-style asynchronously, authenticating if needed + + Request to be executed + The cancellation token + + + + Executes the request asynchronously, authenticating if needed + + Request to be executed + The cancellation token + + + + Default constructor that registers default content handlers + + + + + Sets the BaseUrl property for requests made by this client instance + + + + + + Sets the BaseUrl property for requests made by this client instance + + + + + + Registers a content handler to process response content + + MIME content type of the response content + Deserializer to use to process content + + + + Remove a content handler for the specified MIME content type + + MIME content type to remove + + + + Remove all content handlers + + + + + Retrieve the handler for the specified MIME content type + + MIME content type to retrieve + IDeserializer instance + + + + Assembles URL to call based on parameters, method and resource + + RestRequest to execute + Assembled System.Uri + + + + Executes the specified request and downloads the response data + + Request to execute + Response data + + + + Executes the request and returns a response, authenticating if needed + + Request to be executed + RestResponse + + + + Executes the specified request and deserializes the response content using the appropriate content handler + + Target deserialization type + Request to execute + RestResponse[[T]] with deserialized data in Data property + + + + Maximum number of redirects to follow if FollowRedirects is true + + + + + X509CertificateCollection to be sent with request + + + + + Proxy to use for requests made by this client instance. + Passed on to underlying WebRequest if set. + + + + + The cache policy to use for requests initiated by this client instance. + + + + + Default is true. Determine whether or not requests that result in + HTTP status codes of 3xx should follow returned redirect + + + + + The CookieContainer used for requests made by this client instance + + + + + UserAgent to use for requests made by this client instance + + + + + Timeout in milliseconds to use for requests made by this client instance + + + + + The number of milliseconds before the writing or reading times out. + + + + + Whether to invoke async callbacks using the SynchronizationContext.Current captured when invoked + + + + + Authenticator to use for requests made by this client instance + + + + + Combined with Request.Resource to construct URL for request + Should include scheme and domain without trailing slash. + + + client.BaseUrl = new Uri("http://example.com"); + + + + + Parameters included with every request made with this instance of RestClient + If specified in both client and request, the request wins + + + + + Executes the request and callback asynchronously, authenticating if needed + + The IRestClient this method extends + Request to be executed + Callback function to be executed upon completion + + + + Executes the request and callback asynchronously, authenticating if needed + + The IRestClient this method extends + Target deserialization type + Request to be executed + Callback function to be executed upon completion providing access to the async handle + + + + Add a parameter to use on every request made with this client instance + + The IRestClient instance + Parameter to add + + + + + Removes a parameter from the default parameters that are used on every request made with this client instance + + The IRestClient instance + The name of the parameter that needs to be removed + + + + + Adds a HTTP parameter (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT) + Used on every request made by this client instance + + The IRestClient instance + Name of the parameter + Value of the parameter + This request + + + + Adds a parameter to the request. There are four types of parameters: + - GetOrPost: Either a QueryString value or encoded form value based on method + - HttpHeader: Adds the name/value pair to the HTTP request's Headers collection + - UrlSegment: Inserted into URL if there is a matching url token e.g. {AccountId} + - RequestBody: Used by AddBody() (not recommended to use directly) + + The IRestClient instance + Name of the parameter + Value of the parameter + The type of parameter to add + This request + + + + Shortcut to AddDefaultParameter(name, value, HttpHeader) overload + + The IRestClient instance + Name of the header to add + Value of the header to add + + + + + Shortcut to AddDefaultParameter(name, value, UrlSegment) overload + + The IRestClient instance + Name of the segment to add + Value of the segment to add + + + + + Container for data used to make requests + + + + + Default constructor + + + + + Sets Method property to value of method + + Method to use for this request + + + + Sets Resource property + + Resource to use for this request + + + + Sets Resource and Method properties + + Resource to use for this request + Method to use for this request + + + + Sets Resource property + + Resource to use for this request + + + + Sets Resource and Method properties + + Resource to use for this request + Method to use for this request + + + + Adds a file to the Files collection to be included with a POST or PUT request + (other methods do not support file uploads). + + The parameter name to use in the request + Full path to file to upload + The MIME type of the file to upload + This request + + + + Adds the bytes to the Files collection with the specified file name + + The parameter name to use in the request + The file data + The file name to use for the uploaded file + The MIME type of the file to upload + This request + + + + Adds the bytes to the Files collection with the specified file name and content type + + The parameter name to use in the request + A function that writes directly to the stream. Should NOT close the stream. + The file name to use for the uploaded file + The MIME type of the file to upload + This request + + + + Add bytes to the Files collection as if it was a file of specific type + + A form parameter name + The file data + The file name to use for the uploaded file + Specific content type. Es: application/x-gzip + + + + + Serializes obj to format specified by RequestFormat, but passes xmlNamespace if using the default XmlSerializer + The default format is XML. Change RequestFormat if you wish to use a different serialization format. + + The object to serialize + The XML namespace to use when serializing + This request + + + + Serializes obj to data format specified by RequestFormat and adds it to the request body. + The default format is XML. Change RequestFormat if you wish to use a different serialization format. + + The object to serialize + This request + + + + Serializes obj to JSON format and adds it to the request body. + + The object to serialize + This request + + + + Serializes obj to XML format and adds it to the request body. + + The object to serialize + This request + + + + Serializes obj to format specified by RequestFormat, but passes xmlNamespace if using the default XmlSerializer + Serializes obj to XML format and passes xmlNamespace then adds it to the request body. + + The object to serialize + The XML namespace to use when serializing + This request + + + + Calls AddParameter() for all public, readable properties specified in the includedProperties list + + + request.AddObject(product, "ProductId", "Price", ...); + + The object with properties to add as parameters + The names of the properties to include + This request + + + + Calls AddParameter() for all public, readable properties of obj + + The object with properties to add as parameters + This request + + + + Add the parameter to the request + + Parameter to add + + + + + Adds a HTTP parameter to the request (QueryString for GET, DELETE, OPTIONS and HEAD; Encoded form for POST and PUT) + + Name of the parameter + Value of the parameter + This request + + + + Adds a parameter to the request. There are four types of parameters: + - GetOrPost: Either a QueryString value or encoded form value based on method + - HttpHeader: Adds the name/value pair to the HTTP request's Headers collection + - UrlSegment: Inserted into URL if there is a matching url token e.g. {AccountId} + - RequestBody: Used by AddBody() (not recommended to use directly) + + Name of the parameter + Value of the parameter + The type of parameter to add + This request + + + + Adds a parameter to the request. There are four types of parameters: + - GetOrPost: Either a QueryString value or encoded form value based on method + - HttpHeader: Adds the name/value pair to the HTTP request's Headers collection + - UrlSegment: Inserted into URL if there is a matching url token e.g. {AccountId} + - RequestBody: Used by AddBody() (not recommended to use directly) + + Name of the parameter + Value of the parameter + Content-Type of the parameter + The type of parameter to add + This request + + + + Shortcut to AddParameter(name, value, HttpHeader) overload + + Name of the header to add + Value of the header to add + + + + + Shortcut to AddParameter(name, value, Cookie) overload + + Name of the cookie to add + Value of the cookie to add + + + + + Shortcut to AddParameter(name, value, UrlSegment) overload + + Name of the segment to add + Value of the segment to add + + + + + Shortcut to AddParameter(name, value, QueryString) overload + + Name of the parameter to add + Value of the parameter to add + + + + + Internal Method so that RestClient can increase the number of attempts + + + + + Always send a multipart/form-data request - even when no Files are present. + + + + + Serializer to use when writing JSON request bodies. Used if RequestFormat is Json. + By default the included JsonSerializer is used (currently using JSON.NET default serialization). + + + + + Serializer to use when writing XML request bodies. Used if RequestFormat is Xml. + By default the included XmlSerializer is used. + + + + + Set this to write response to Stream rather than reading into memory. + + + + + Determine whether or not the "default credentials" (e.g. the user account under which the current process is running) + will be sent along to the server. The default is false. + + + + + Container of all HTTP parameters to be passed with the request. + See AddParameter() for explanation of the types of parameters that can be passed + + + + + Container of all the files to be uploaded with the request. + + + + + Determines what HTTP method to use for this request. Supported methods: GET, POST, PUT, DELETE, HEAD, OPTIONS + Default is GET + + + + + The Resource URL to make the request against. + Tokens are substituted with UrlSegment parameters and match by name. + Should not include the scheme or domain. Do not include leading slash. + Combined with RestClient.BaseUrl to assemble final URL: + {BaseUrl}/{Resource} (BaseUrl is scheme + domain, e.g. http://example.com) + + + // example for url token replacement + request.Resource = "Products/{ProductId}"; + request.AddParameter("ProductId", 123, ParameterType.UrlSegment); + + + + + Serializer to use when writing XML request bodies. Used if RequestFormat is Xml. + By default XmlSerializer is used. + + + + + Used by the default deserializers to determine where to start deserializing from. + Can be used to skip container or root elements that do not have corresponding deserialzation targets. + + + + + A function to run prior to deserializing starting (e.g. change settings if error encountered) + + + + + Used by the default deserializers to explicitly set which date format string to use when parsing dates. + + + + + Used by XmlDeserializer. If not specified, XmlDeserializer will flatten response by removing namespaces from element names. + + + + + In general you would not need to set this directly. Used by the NtlmAuthenticator. + + + + + Gets or sets a user-defined state object that contains information about a request and which can be later + retrieved when the request completes. + + + + + Timeout in milliseconds to be used for the request. This timeout value overrides a timeout set on the RestClient. + + + + + The number of milliseconds before the writing or reading times out. This timeout value overrides a timeout set on the RestClient. + + + + + How many attempts were made to send this Request? + + + This Number is incremented each time the RestClient sends the request. + Useful when using Asynchronous Execution with Callbacks + + + + + Base class for common properties shared by RestResponse and RestResponse[[T]] + + + + + Default constructor + + + + + Assists with debugging responses by displaying in the debugger output + + + + + + The RestRequest that was made to get this RestResponse + + + Mainly for debugging if ResponseStatus is not OK + + + + + MIME content type of response + + + + + Length in bytes of the response content + + + + + Encoding of the response content + + + + + String representation of response content + + + + + HTTP response status code + + + + + Description of HTTP status returned + + + + + Response content + + + + + The URL that actually responded to the content (different from request if redirected) + + + + + HttpWebResponse.Server + + + + + Cookies returned by server with the response + + + + + Headers returned by server with the response + + + + + Status of the request. Will return Error for transport errors. + HTTP errors will still return ResponseStatus.Completed, check StatusCode instead + + + + + Transport or other non-HTTP error generated while attempting request + + + + + The exception thrown during the request, if any + + + + + Container for data sent back from API including deserialized data + + Type of data to deserialize to + + + + Deserialized entity data + + + + + Container for data sent back from API + + + + + Comment of the cookie + + + + + Comment of the cookie + + + + + Indicates whether the cookie should be discarded at the end of the session + + + + + Domain of the cookie + + + + + Indicates whether the cookie is expired + + + + + Date and time that the cookie expires + + + + + Indicates that this cookie should only be accessed by the server + + + + + Name of the cookie + + + + + Path of the cookie + + + + + Port of the cookie + + + + + Indicates that the cookie should only be sent over secure channels + + + + + Date and time the cookie was created + + + + + Value of the cookie + + + + + Version of the cookie + + + + + Wrapper for System.Xml.Serialization.XmlSerializer. + + + + + Default constructor, does not specify namespace + + + + + Specify the namespaced to be used when serializing + + XML namespace + + + + Serialize the object as XML + + Object to serialize + XML as string + + + + Name of the root element to use when serializing + + + + + XML namespace to use when serializing + + + + + Format string to use when serializing dates + + + + + Content type for serialized content + + + + + Encoding for serialized content + + + + + Need to subclass StringWriter in order to override Encoding + + + + + Default JSON serializer for request bodies + Doesn't currently use the SerializeAs attribute, defers to Newtonsoft's attributes + + + + + Default serializer + + + + + Serialize the object as JSON + + Object to serialize + JSON as String + + + + Unused for JSON Serialization + + + + + Unused for JSON Serialization + + + + + Unused for JSON Serialization + + + + + Content type for serialized content + + + + + Allows control how class and property names and values are serialized by XmlSerializer + Currently not supported with the JsonSerializer + When specified at the property level the class-level specification is overridden + + + + + Called by the attribute when NameStyle is speficied + + The string to transform + String + + + + The name to use for the serialized element + + + + + Sets the value to be serialized as an Attribute instead of an Element + + + + + The culture to use when serializing + + + + + Transforms the casing of the name based on the selected value. + + + + + The order to serialize the element. Default is int.MaxValue. + + + + + Options for transforming casing of element names + + + + + Default XML Serializer + + + + + Default constructor, does not specify namespace + + + + + Specify the namespaced to be used when serializing + + XML namespace + + + + Serialize the object as XML + + Object to serialize + XML as string + + + + Determines if a given object is numeric in any way + (can be integer, double, null, etc). + + + + + Name of the root element to use when serializing + + + + + XML namespace to use when serializing + + + + + Format string to use when serializing dates + + + + + Content type for serialized content + + + + + Helper methods for validating required values + + + + + Require a parameter to not be null + + Name of the parameter + Value of the parameter + + + + Represents the json array. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + The capacity of the json array. + + + + The json representation of the array. + + The json representation of the array. + + + + Represents the json object. + + + + + The internal member dictionary. + + + + + Initializes a new instance of . + + + + + Initializes a new instance of . + + The implementation to use when comparing keys, or null to use the default for the type of the key. + + + + Adds the specified key. + + The key. + The value. + + + + Determines whether the specified key contains key. + + The key. + + true if the specified key contains key; otherwise, false. + + + + + Removes the specified key. + + The key. + + + + + Tries the get value. + + The key. + The value. + + + + + Adds the specified item. + + The item. + + + + Clears this instance. + + + + + Determines whether [contains] [the specified item]. + + The item. + + true if [contains] [the specified item]; otherwise, false. + + + + + Copies to. + + The array. + Index of the array. + + + + Removes the specified item. + + The item. + + + + + Gets the enumerator. + + + + + + Returns an enumerator that iterates through a collection. + + + An object that can be used to iterate through the collection. + + + + + Returns a json that represents the current . + + + A json that represents the current . + + + + + Provides implementation for type conversion operations. Classes derived from the class can override this method to specify dynamic behavior for operations that convert an object from one type to another. + + Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the class, binder.Type returns the type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion. + The result of the type conversion operation. + + Alwasy returns true. + + + + + Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. + + Provides information about the deletion. + + Alwasy returns true. + + + + + Provides the implementation for operations that get a value by index. Classes derived from the class can override this method to specify dynamic behavior for indexing operations. + + Provides information about the operation. + The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, is equal to 3. + The result of the index operation. + + Alwasy returns true. + + + + + Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. + + Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. + The result of the get operation. For example, if the method is called for a property, you can assign the property value to . + + Alwasy returns true. + + + + + Provides the implementation for operations that set a value by index. Classes derived from the class can override this method to specify dynamic behavior for operations that access objects by a specified index. + + Provides information about the operation. + The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 3. + The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 10. + + true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. + + + + + Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. + + Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. + The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". + + true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) + + + + + Returns the enumeration of all dynamic member names. + + + A sequence that contains dynamic member names. + + + + + Gets the at the specified index. + + + + + + Gets the keys. + + The keys. + + + + Gets the values. + + The values. + + + + Gets or sets the with the specified key. + + + + + + Gets the count. + + The count. + + + + Gets a value indicating whether this instance is read only. + + + true if this instance is read only; otherwise, false. + + + + + This class encodes and decodes JSON strings. + Spec. details, see http://www.json.org/ + + JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). + All numbers are parsed to doubles. + + + + + Parses the string json into a value + + A JSON string. + An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false + + + + Try parsing the json string into a value. + + + A JSON string. + + + The object. + + + Returns true if successfull otherwise false. + + + + + Converts a IDictionary<string,object> / IList<object> object into a JSON string + + A IDictionary<string,object> / IList<object> + Serializer strategy to use + A JSON encoded string, or null if object 'json' is not serializable + + + + Determines if a given object is numeric in any way + (can be integer, double, null, etc). + + + + + Helper methods for validating values + + + + + Validate an integer value is between the specified values (exclusive of min/max) + + Value to validate + Exclusive minimum value + Exclusive maximum value + + + + Validate a string length + + String to be validated + Maximum length of the string + + + diff --git a/WizBot/bin/Debug/System.Net.Http.Formatting.xml b/WizBot/bin/Debug/System.Net.Http.Formatting.xml new file mode 100644 index 000000000..3fb65976c --- /dev/null +++ b/WizBot/bin/Debug/System.Net.Http.Formatting.xml @@ -0,0 +1,2094 @@ + + + + System.Net.Http.Formatting + + + + + implementation which provides a byte range view over a stream used to generate HTTP 206 (Partial Content) byte range responses. The supports one or more byte ranges regardless of whether the ranges are consecutive or not. If there is only one range then a single partial response body containing a Content-Range header is generated. If there are more than one ranges then a multipart/byteranges response is generated where each body part contains a range indicated by the associated Content-Range header field. + + + + implementation which provides a byte range view over a stream used to generate HTTP 206 (Partial Content) byte range responses. If none of the requested ranges overlap with the current extend of the selected resource represented by the content parameter then an is thrown indicating the valid Content-Range of the content. + The stream over which to generate a byte range view. + The range or ranges, typically obtained from the Range HTTP request header field. + The media type of the content stream. + + + + implementation which provides a byte range view over a stream used to generate HTTP 206 (Partial Content) byte range responses. If none of the requested ranges overlap with the current extend of the selected resource represented by the content parameter then an is thrown indicating the valid Content-Range of the content. + The stream over which to generate a byte range view. + The range or ranges, typically obtained from the Range HTTP request header field. + The media type of the content stream. + The buffer size used when copying the content stream. + + + + implementation which provides a byte range view over a stream used to generate HTTP 206 (Partial Content) byte range responses. If none of the requested ranges overlap with the current extend of the selected resource represented by the content parameter then an is thrown indicating the valid Content-Range of the content. + The stream over which to generate a byte range view. + The range or ranges, typically obtained from the Range HTTP request header field. + The media type of the content stream. + + + + implementation which provides a byte range view over a stream used to generate HTTP 206 (Partial Content) byte range responses. If none of the requested ranges overlap with the current extend of the selected resource represented by the content parameter then an is thrown indicating the valid Content-Range of the content. + The stream over which to generate a byte range view. + The range or ranges, typically obtained from the Range HTTP request header field. + The media type of the content stream. + The buffer size used when copying the content stream. + + + Releases the resources used by the current instance of the class. + true to release managed and unmanaged resources; false to release only unmanaged resources. + + + Asynchronously serialize and write the byte range to an HTTP content stream. + The task object representing the asynchronous operation. + The target stream. + Information about the transport. + + + Determines whether a byte array has a valid length in bytes. + true if length is a valid length; otherwise, false. + The length in bytes of the byte array. + + + Extension methods that aid in making formatted requests using . + + + + + + + + + Sends a POST request as an asynchronous operation to the specified Uri with the given value serialized as JSON. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The type of value. + + + Sends a POST request as an asynchronous operation to the specified Uri with the given value serialized as JSON. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + + + + + + + Sends a POST request as an asynchronous operation to the specified Uri with the given value serialized as XML. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The type of value. + + + Sends a POST request as an asynchronous operation to the specified Uri with the given value serialized as XML. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + + + + + + + + + + + + + + + + Sends a POST request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + The type of value. + + + Sends a POST request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + The authoritative value of the request's content's Content-Type header. Can be null in which case the <paramref name="formatter">formatter's</paramref> default content type will be used. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + Sends a POST request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + The authoritative value of the request's content's Content-Type header. Can be null in which case the <paramref name="formatter">formatter's</paramref> default content type will be used. + The type of value. + + + Sends a POST request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + The authoritative value of the request's content's Content-Type header. Can be null in which case the <paramref name="formatter">formatter's</paramref> default content type will be used. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + Sends a POST request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + + + + + + + Sends a PUT request as an asynchronous operation to the specified Uri with the given value serialized as JSON. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The type of value. + + + Sends a PUT request as an asynchronous operation to the specified Uri with the given value serialized as JSON. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + + + + + + + Sends a PUT request as an asynchronous operation to the specified Uri with the given value serialized as XML. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The type of value. + + + Sends a PUT request as an asynchronous operation to the specified Uri with the given value serialized as XML. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + + + + + + + + + + + + + + + + Sends a PUT request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + The type of value. + + + Sends a PUT request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + The authoritative value of the request's content's Content-Type header. Can be null in which case the <paramref name="formatter">formatter's</paramref> default content type will be used. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + Sends a PUT request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + The authoritative value of the request's content's Content-Type header. Can be null in which case the <paramref name="formatter">formatter's</paramref> default content type will be used. + The type of value. + + + Sends a PUT request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + The authoritative value of the request's content's Content-Type header. Can be null in which case the <paramref name="formatter">formatter's</paramref> default content type will be used. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + Sends a PUT request as an asynchronous operation to the specified Uri with value serialized using the given formatter. + A task object representing the asynchronous operation. + The client used to make the request. + The Uri the request is sent to. + The value that will be placed in the request's entity body. + The formatter used to serialize the value. + A cancellation token that can be used by other objects or threads to receive notice of cancellation. + The type of value. + + + Represents the factory for creating new instance of . + + + Creates a new instance of the . + A new instance of the . + The list of HTTP handler that delegates the processing of HTTP response messages to another handler. + + + Creates a new instance of the . + A new instance of the . + The inner handler which is responsible for processing the HTTP response messages. + The list of HTTP handler that delegates the processing of HTTP response messages to another handler. + + + Creates a new instance of the which should be pipelined. + A new instance of the which should be pipelined. + The inner handler which is responsible for processing the HTTP response messages. + The list of HTTP handler that delegates the processing of HTTP response messages to another handler. + + + Specifies extension methods to allow strongly typed objects to be read from HttpContent instances. + + + Returns a Task that will yield an object of the specified type <typeparamref name="T" /> from the content instance. + An object instance of the specified type. + The HttpContent instance from which to read. + The type of the object to read. + + + Returns a Task that will yield an object of the specified type <typeparamref name="T" /> from the content instance. + An object instance of the specified type. + The HttpContent instance from which to read. + The collection of MediaTyepFormatter instances to use. + The type of the object to read. + + + Returns a Task that will yield an object of the specified type <typeparamref name="T" /> from the content instance. + An object instance of the specified type. + The HttpContent instance from which to read. + The collection of MediaTypeFormatter instances to use. + The IFormatterLogger to log events to. + The type of the object to read. + + + Returns a Task that will yield an object of the specified type from the content instance. + An object instance of the specified type. + The HttpContent instance from which to read. + The collection of MediaTypeFormatter instances to use. + The IFormatterLogger to log events to. + The token to cancel the operation. + The type of the object to read. + + + Returns a Task that will yield an object of the specified type from the content instance. + An object instance of the specified type. + The HttpContent instance from which to read. + The collection of MediaTypeFormatter instances to use. + The token to cancel the operation. + The type of the object to read. + + + Returns a Task that will yield an object of the specified type from the content instance. + An object instance of the specified type. + The HttpContent instance from which to read. + The token to cancel the operation. + The type of the object to read. + + + Returns a Task that will yield an object of the specified type from the content instance. + A Task that will yield an object instance of the specified type. + The HttpContent instance from which to read. + The type of the object to read. + + + Returns a Task that will yield an object of the specified type from the content instance using one of the provided formatters to deserialize the content. + An object instance of the specified type. + The HttpContent instance from which to read. + The type of the object to read. + The collection of MediaTypeFormatter instances to use. + + + Returns a Task that will yield an object of the specified type from the content instance using one of the provided formatters to deserialize the content. + An object instance of the specified type. + The HttpContent instance from which to read. + The type of the object to read. + The collection of MediaTypeFormatter instances to use. + The IFormatterLogger to log events to. + + + Returns a Task that will yield an object of the specified type from the content instance using one of the provided formatters to deserialize the content. + An object instance of the specified type. + The HttpContent instance from which to read. + The type of the object to read. + The collection of MediaTypeFormatter instances to use. + The IFormatterLogger to log events to. + The token to cancel the operation. + + + Returns a Task that will yield an object of the specified type from the content instance using one of the provided formatters to deserialize the content. + An object instance of the specified type. + The HttpContent instance from which to read. + The type of the object to read. + The collection of MediaTypeFormatter instances to use. + The token to cancel the operation. + + + Returns a Task that will yield an object of the specified type from the content instance using one of the provided formatters to deserialize the content. + An object instance of the specified type. + The HttpContent instance from which to read. + The type of the object to read. + The token to cancel the operation. + + + Extension methods to read HTML form URL-encoded datafrom instances. + + + Determines whether the specified content is HTML form URL-encoded data. + true if the specified content is HTML form URL-encoded data; otherwise, false. + The content. + + + Asynchronously reads HTML form URL-encoded from an instance and stores the results in a object. + A task object representing the asynchronous operation. + The content. + + + Asynchronously reads HTML form URL-encoded from an instance and stores the results in a object. + A task object representing the asynchronous operation. + The content. + The token to cancel the operation. + + + Provides extension methods to read and entities from instances. + + + Determines whether the specified content is HTTP request message content. + true if the specified content is HTTP message content; otherwise, false. + The content to check. + + + Determines whether the specified content is HTTP response message content. + true if the specified content is HTTP message content; otherwise, false. + The content to check. + + + Reads the as an . + The parsed instance. + The content to read. + + + Reads the as an . + The parsed instance. + The content to read. + The URI scheme to use for the request URI. + + + Reads the as an . + The parsed instance. + The content to read. + The URI scheme to use for the request URI. + The size of the buffer. + + + Reads the as an . + The parsed instance. + The content to read. + The URI scheme to use for the request URI. + The size of the buffer. + The maximum length of the HTTP header. + + + + + + + Reads the as an . + The parsed instance. + The content to read. + + + Reads the as an . + The parsed instance. + The content to read. + The size of the buffer. + + + Reads the as an . + The parsed instance. + The content to read. + The size of the buffer. + The maximum length of the HTTP header. + + + + + + Extension methods to read MIME multipart entities from instances. + + + Determines whether the specified content is MIME multipart content. + true if the specified content is MIME multipart content; otherwise, false. + The content. + + + Determines whether the specified content is MIME multipart content with the specified subtype. + true if the specified content is MIME multipart content with the specified subtype; otherwise, false. + The content. + The MIME multipart subtype to match. + + + Reads all body parts within a MIME multipart message and produces a set of instances as a result. + A representing the tasks of getting the collection of instances where each instance represents a body part. + An existing instance to use for the object's content. + + + Reads all body parts within a MIME multipart message and produces a set of instances as a result. + A representing the tasks of getting the collection of instances where each instance represents a body part. + An existing instance to use for the object's content. + The token to cancel the operation. + + + Reads all body parts within a MIME multipart message and produces a set of instances as a result using the streamProvider instance to determine where the contents of each body part is written. + A representing the tasks of getting the collection of instances where each instance represents a body part. + An existing instance to use for the object's content. + A stream provider providing output streams for where to write body parts as they are parsed. + The type of the MIME multipart. + + + Reads all body parts within a MIME multipart message and produces a set of instances as a result using the streamProvider instance to determine where the contents of each body part is written and bufferSize as read buffer size. + A representing the tasks of getting the collection of instances where each instance represents a body part. + An existing instance to use for the object's content. + A stream provider providing output streams for where to write body parts as they are parsed. + Size of the buffer used to read the contents. + The type of the MIME multipart. + + + Reads all body parts within a MIME multipart message and produces a set of instances as a result using the streamProvider instance to determine where the contents of each body part is written and bufferSize as read buffer size. + A representing the tasks of getting the collection of instances where each instance represents a body part. + An existing instance to use for the object's content. + A stream provider providing output streams for where to write body parts as they are parsed. + Size of the buffer used to read the contents. + The token to cancel the operation. + The type of the MIME multipart. + + + Reads all body parts within a MIME multipart message and produces a set of instances as a result using the streamProvider instance to determine where the contents of each body part is written. + A representing the tasks of getting the collection of instances where each instance represents a body part. + An existing instance to use for the object's content. + A stream provider providing output streams for where to write body parts as they are parsed. + The token to cancel the operation. + The type of the MIME multipart. + + + Derived class which can encapsulate an or an as an entity with media type "application/http". + + + Initializes a new instance of the class encapsulating an . + The instance to encapsulate. + + + Initializes a new instance of the class encapsulating an . + The instance to encapsulate. + + + Releases unmanaged and - optionally - managed resources + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Gets the HTTP request message. + + + Gets the HTTP response message. + + + Asynchronously serializes the object's content to the given stream. + A instance that is asynchronously serializing the object's content. + The to which to write. + The associated . + + + Computes the length of the stream if possible. + true if the length has been computed; otherwise false. + The computed length of the stream. + + + Provides extension methods for the class. + + + Gets any cookie headers present in the request. + A collection of instances. + The request headers. + + + Gets any cookie headers present in the request that contain a cookie state whose name that matches the specified value. + A collection of instances. + The request headers. + The cookie state name to match. + + + + + Provides extension methods for the class. + + + Adds cookies to a response. Each Set-Cookie header is represented as one instance. A contains information about the domain, path, and other cookie information as well as one or more instances. Each instance contains a cookie name and whatever cookie state is associate with that name. The state is in the form of a which on the wire is encoded as HTML Form URL-encoded data. This representation allows for multiple related "cookies" to be carried within the same Cookie header while still providing separation between each cookie state. A sample Cookie header is shown below. In this example, there are two with names state1 and state2 respectively. Further, each cookie state contains two name/value pairs (name1/value1 and name2/value2) and (name3/value3 and name4/value4). <code> Set-Cookie: state1:name1=value1&amp;name2=value2; state2:name3=value3&amp;name4=value4; domain=domain1; path=path1; </code> + The response headers + The cookie values to add to the response. + + + An exception thrown by in case none of the requested ranges overlap with the current extend of the selected resource. The current extend of the resource is indicated in the ContentRange property. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + + + The current extend of the resource indicated in terms of a ContentRange header field. + + + Represents a multipart file data. + + + Initializes a new instance of the class. + The headers of the multipart file data. + The name of the local file for the multipart file data. + + + Gets or sets the headers of the multipart file data. + The headers of the multipart file data. + + + Gets or sets the name of the local file for the multipart file data. + The name of the local file for the multipart file data. + + + Represents an suited for writing each MIME body parts of the MIME multipart message to a file using a . + + + Initializes a new instance of the class. + The root path where the content of MIME multipart body parts are written to. + + + Initializes a new instance of the class. + The root path where the content of MIME multipart body parts are written to. + The number of bytes buffered for writes to the file. + + + Gets or sets the number of bytes buffered for writes to the file. + The number of bytes buffered for writes to the file. + + + Gets or sets the multipart file data. + The multipart file data. + + + Gets the name of the local file which will be combined with the root path to create an absolute file name where the contents of the current MIME body part will be stored. + A relative filename with no path component. + The headers for the current MIME body part. + + + Gets the stream instance where the message body part is written to. + The instance where the message body part is written to. + The content of HTTP. + The header fields describing the body part. + + + Gets or sets the root path where the content of MIME multipart body parts are written to. + The root path where the content of MIME multipart body parts are written to. + + + A implementation suited for use with HTML file uploads for writing file content to a remote storage . The stream provider looks at the Content-Disposition header field and determines an output remote based on the presence of a filename parameter. If a filename parameter is present in the Content-Disposition header field, then the body part is written to a remote provided by . Otherwise it is written to a . + + + Initializes a new instance of the class. + + + Read the non-file contents as form data. + A representing the post processing. + + + Read the non-file contents as form data. + A representing the post processing. + The token to monitor for cancellation requests. + + + Gets a collection of file data passed as part of the multipart form data. + + + Gets a of form data passed as part of the multipart form data. + + + Provides a for . Override this method to provide a remote stream to which the data should be written. + A result specifying a remote stream where the file will be written to and a location where the file can be accessed. It cannot be null and the stream must be writable. + The parent MIME multipart instance. + The header fields describing the body part's content. + + + + Represents an suited for use with HTML file uploads for writing file content to a . + + + Initializes a new instance of the class. + The root path where the content of MIME multipart body parts are written to. + + + Initializes a new instance of the class. + The root path where the content of MIME multipart body parts are written to. + The number of bytes buffered for writes to the file. + + + Reads the non-file contents as form data. + A task that represents the asynchronous operation. + + + + Gets a of form data passed as part of the multipart form data. + The of form data. + + + Gets the streaming instance where the message body part is written. + The instance where the message body part is written. + The HTTP content that contains this body part. + Header fields describing the body part. + + + Represents a multipart memory stream provider. + + + Initializes a new instance of the class. + + + Returns the for the . + The for the . + A object. + The HTTP content headers. + + + Represents the provider for the multipart related multistream. + + + Initializes a new instance of the class. + + + Gets the related stream for the provider. + The content headers. + The parent content. + The http content headers. + + + Gets the root content of the . + The root content of the . + + + Represents a multipart file data for remote storage. + + + Initializes a new instance of the class. + The headers of the multipart file data. + The remote file's location. + The remote file's name. + + + Gets the remote file's name. + + + Gets the headers of the multipart file data. + + + Gets the remote file's location. + + + Represents a stream provider that examines the headers provided by the MIME multipart parser as part of the MIME multipart extension methods (see ) and decides what kind of stream to return for the body part to be written to. + + + Initializes a new instance of the class. + + + Gets or sets the contents for this . + The contents for this . + + + Executes the post processing operation for this . + The asynchronous task for this operation. + + + Executes the post processing operation for this . + The asynchronous task for this operation. + The token to cancel the operation. + + + Gets the stream where to write the body part to. This method is called when a MIME multipart body part has been parsed. + The instance where the message body part is written to. + The content of the HTTP. + The header fields describing the body part. + + + Contains a value as well as an associated that will be used to serialize the value when writing this content. + + + Initializes a new instance of the class. + The type of object this instance will contain. + The value of the object this instance will contain. + The formatter to use when serializing the value. + + + Initializes a new instance of the class. + The type of object this instance will contain. + The value of the object this instance will contain. + The formatter to use when serializing the value. + The authoritative value of the Content-Type header. Can be null, in which case the default content type of the formatter will be used. + + + Initializes a new instance of the class. + The type of object this instance will contain. + The value of the object this instance will contain. + The formatter to use when serializing the value. + The authoritative value of the Content-Type header. + + + Gets the media-type formatter associated with this content instance. + The media type formatter associated with this content instance. + + + Gets the type of object managed by this instance. + The object type. + + + Asynchronously serializes the object's content to the given stream. + The task object representing the asynchronous operation. + The stream to write to. + The associated . + + + Computes the length of the stream if possible. + true if the length has been computed; otherwise, false. + Receives the computed length of the stream. + + + Gets or sets the value of the content. + The content value. + + + Generic form of . + The type of object this class will contain. + + + Initializes a new instance of the class. + The value of the object this instance will contain. + The formatter to use when serializing the value. + + + Initializes a new instance of the <see cref="T:System.Net.Http.ObjectContent`1" /> class. + The value of the object this instance will contain. + The formatter to use when serializing the value. + The authoritative value of the Content-Type header. Can be null, in which case the default content type of the formatter will be used. + + + Initializes a new instance of the class. + The value of the object this instance will contain. + The formatter to use when serializing the value. + The authoritative value of the Content-Type header. + + + Enables scenarios where a data producer wants to write directly (either synchronously or asynchronously) using a stream. + + + Initializes a new instance of the class. + An action that is called when an output stream is available, allowing the action to write to it directly. + + + Initializes a new instance of the class. + An action that is called when an output stream is available, allowing the action to write to it directly. + The media type. + + + Initializes a new instance of the class. + An action that is called when an output stream is available, allowing the action to write to it directly. + The media type. + + + Initializes a new instance of the class. + An action that is called when an output stream is available, allowing the action to write to it directly. + + + Initializes a new instance of the class. + An action that is called when an output stream is available, allowing the action to write to it directly. + The media type. + + + Initializes a new instance of the class. + An action that is called when an output stream is available, allowing the action to write to it directly. + The media type. + + + Asynchronously serializes the push content into stream. + The serialized push content. + The stream where the push content will be serialized. + The context. + + + Determines whether the stream content has a valid length in bytes. + true if length is a valid length; otherwise, false. + The length in bytes of the stream content. + + + Represents the result for . + + + Initializes a new instance of the class. + The remote stream instance where the file will be written to. + The remote file's location. + The remote file's name. + + + Gets the remote file's location. + + + Gets the remote file's location. + + + Gets the remote stream instance where the file will be written to. + + + Defines an exception type for signalling that a request's media type was not supported. + + + Initializes a new instance of the class. + The message that describes the error. + The unsupported media type. + + + Gets or sets the media type. + The media type. + + + Contains extension methods to allow strongly typed objects to be read from the query component of instances. + + + Parses the query portion of the specified URI. + A that contains the query parameters. + The URI to parse. + + + Reads HTML form URL encoded data provided in the URI query string as an object of a specified type. + true if the query component of the URI can be read as the specified type; otherwise, false. + The URI to read. + The type of object to read. + When this method returns, contains an object that is initialized from the query component of the URI. This parameter is treated as uninitialized. + + + Reads HTML form URL encoded data provided in the URI query string as an object of a specified type. + true if the query component of the URI can be read as the specified type; otherwise, false. + The URI to read. + When this method returns, contains an object that is initialized from the query component of the URI. This parameter is treated as uninitialized. + The type of object to read. + + + Reads HTML form URL encoded data provided in the query component as a object. + true if the query component can be read as ; otherwise false. + The instance from which to read. + An object to be initialized with this instance or null if the conversion cannot be performed. + + + Abstract media type formatter class to support Bson and Json. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The instance to copy settings from. + + + Determines whether this formatter can read objects of the specified type. + true if objects of this type can be read, otherwise false. + The type of object that will be read. + + + Determines whether this formatter can write objects of the specified type. + true if objects of this type can be written, otherwise false. + The type of object to write. + + + Creates a instance with the default settings used by the . + Returns . + + + Called during deserialization to get the . + The reader to use during deserialization. + The type of the object to read. + The stream from which to read. + The encoding to use when reading. + + + Called during serialization and deserialization to get the . + The JsonSerializer used during serialization and deserialization. + + + Called during serialization to get the . + The writer to use during serialization. + The type of the object to write. + The stream to write to. + The encoding to use when writing. + + + Gets or sets the maximum depth allowed by this formatter. + The maximum depth allowed by this formatter. + + + Called during deserialization to read an object of the specified type from the specified stream. + The object that has been read. + The type of the object to read. + The stream from which to read. + The encoding to use when reading. + The logger to log events to. + + + Called during deserialization to read an object of the specified type from the specified stream. + A task whose result will be the object instance that has been read. + The type of the object to read. + The stream from which to read. + The for the content being read. + The logger to log events to. + + + Gets or sets the JsonSerializerSettings used to configure the JsonSerializer. + The JsonSerializerSettings used to configure the JsonSerializer. + + + Called during serialization to write an object of the specified type to the specified stream. + The type of the object to write. + The object to write. + The stream to write to. + The encoding to use when writing. + + + Called during serialization to write an object of the specified type to the specified stream. + Returns . + The type of the object to write. + The object to write. + The stream to write to. + The for the content being written. + The transport context. + The token to monitor for cancellation. + + + Represents a media type formatter to handle Bson. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The formatter to copy settings from. + + + Called during deserialization to get the . + The reader to use during deserialization. + The type of the object to read. + The stream from which to read. + The encoding to use when reading. + + + Called during serialization to get the . + The writer to use during serialization. + The type of the object to write. + The stream to write to. + The encoding to use when writing. + + + Gets the default media type for Json, namely "application/bson". + The default media type for Json, namely "application/bson". + + + Gets or sets the maximum depth allowed by this formatter. + The maximum depth allowed by this formatter. + + + Called during deserialization to read an object of the specified type from the specified stream. + The object that has been read. + The type of the object to read. + The stream from which to read. + The encoding to use when reading. + The logger to log events to. + + + Called during deserialization to read an object of the specified type from the specified stream. + A task whose result will be the object instance that has been read. + The type of the object to read. + The stream from which to read. + The for the content being read. + The logger to log events to. + + + Called during serialization to write an object of the specified type to the specified stream. + The type of the object to write. + The object to write. + The stream to write to. + The encoding to use when writing. + + + Represents a helper class to allow a synchronous formatter on top of the asynchronous formatter infrastructure. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The instance to copy settings from. + + + Gets or sets the suggested size of buffer to use with streams in bytes. + The suggested size of buffer to use with streams in bytes. + + + Reads synchronously from the buffered stream. + An object of the given . + The type of the object to deserialize. + The stream from which to read. + The , if available. Can be null. + The to log events to. + + + Reads synchronously from the buffered stream. + An object of the given . + The type of the object to deserialize. + The stream from which to read. + The , if available. Can be null. + The to log events to. + The token to cancel the operation. + + + Reads asynchronously from the buffered stream. + A task object representing the asynchronous operation. + The type of the object to deserialize. + The stream from which to read. + The , if available. Can be null. + The to log events to. + + + Reads asynchronously from the buffered stream. + A task object representing the asynchronous operation. + The type of the object to deserialize. + The stream from which to read. + The , if available. Can be null. + The to log events to. + The token to cancel the operation. + + + Writes synchronously to the buffered stream. + The type of the object to serialize. + The object value to write. Can be null. + The stream to which to write. + The , if available. Can be null. + + + Writes synchronously to the buffered stream. + The type of the object to serialize. + The object value to write. Can be null. + The stream to which to write. + The , if available. Can be null. + The token to cancel the operation. + + + Writes asynchronously to the buffered stream. + A task object representing the asynchronous operation. + The type of the object to serialize. + The object value to write. It may be null. + The stream to which to write. + The , if available. Can be null. + The transport context. + + + Writes asynchronously to the buffered stream. + A task object representing the asynchronous operation. + The type of the object to serialize. + The object value to write. It may be null. + The stream to which to write. + The , if available. Can be null. + The transport context. + The token to cancel the operation. + + + Represents the result of content negotiation performed using <see cref="M:System.Net.Http.Formatting.IContentNegotiator.Negotiate(System.Type,System.Net.Http.HttpRequestMessage,System.Collections.Generic.IEnumerable{System.Net.Http.Formatting.MediaTypeFormatter})" /> + + + Create the content negotiation result object. + The formatter. + The preferred media type. Can be null. + + + The formatter chosen for serialization. + + + The media type that is associated with the formatter chosen for serialization. Can be null. + + + The default implementation of , which is used to select a for an or . + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + true to exclude formatters that match only on the object type; otherwise, false. + + + Determines how well each formatter matches an HTTP request. + Returns a collection of objects that represent all of the matches. + The type to be serialized. + The request. + The set of objects from which to choose. + + + If true, exclude formatters that match only on the object type; otherwise, false. + Returns a . + + + Matches a set of Accept header fields against the media types that a formatter supports. + Returns a object that indicates the quality of the match, or null if there is no match. + A list of Accept header values, sorted in descending order of q factor. You can create this list by calling the method. + The formatter to match against. + + + Matches a request against the objects in a media-type formatter. + Returns a object that indicates the quality of the match, or null if there is no match. + The request to match. + The media-type formatter. + + + Match the content type of a request against the media types that a formatter supports. + Returns a object that indicates the quality of the match, or null if there is no match. + The request to match. + The formatter to match against. + + + Selects the first supported media type of a formatter. + Returns a with set to MatchOnCanWriteType, or null if there is no match. A indicating the quality of the match or null is no match. + The type to match. + The formatter to match against. + + + Performs content negotiating by selecting the most appropriate out of the passed in for the given that can serialize an object of the given . + The result of the negotiation containing the most appropriate instance, or null if there is no appropriate formatter. + The type to be serialized. + The request. + The set of objects from which to choose. + + + Determines the best character encoding for writing the response. + Returns the that is the best match. + The request. + The selected media formatter. + + + Select the best match among the candidate matches found. + Returns the object that represents the best match. + The collection of matches. + + + Determine whether to match on type or not. This is used to determine whether to generate a 406 response or use the default media type formatter in case there is no match against anything in the request. If ExcludeMatchOnTypeOnly is true then we don't match on type unless there are no accept headers. + True if not ExcludeMatchOnTypeOnly and accept headers with a q-factor bigger than 0.0 are present. + The sorted accept header values to match. + + + Sorts Accept header values in descending order of q factor. + Returns the sorted list of MediaTypeWithQualityHeaderValue objects. + A collection of StringWithQualityHeaderValue objects, representing the header fields. + + + Sorts a list of Accept-Charset, Accept-Encoding, Accept-Language or related header values in descending order or q factor. + Returns the sorted list of StringWithQualityHeaderValue objects. + A collection of StringWithQualityHeaderValue objects, representing the header fields. + + + Evaluates whether a match is better than the current match. + Returns whichever object is a better match. + The current match. + The match to evaluate against the current match. + + + Helper class to serialize <see cref="T:System.Collections.Generic.IEnumerable`1" /> types by delegating them through a concrete implementation."/&gt;. + The interface implementing to proxy. + + + Initialize a DelegatingEnumerable. This constructor is necessary for to work. + + + Initialize a DelegatingEnumerable with an <see cref="T:System.Collections.Generic.IEnumerable`1" />. This is a helper class to proxy <see cref="T:System.Collections.Generic.IEnumerable`1" /> interfaces for . + The <see cref="T:System.Collections.Generic.IEnumerable`1" /> instance to get the enumerator from. + + + This method is not implemented but is required method for serialization to work. Do not use. + The item to add. Unused. + + + Get the enumerator of the associated <see cref="T:System.Collections.Generic.IEnumerable`1" />. + The enumerator of the <see cref="T:System.Collections.Generic.IEnumerable`1" /> source. + + + Get the enumerator of the associated <see cref="T:System.Collections.Generic.IEnumerable`1" />. + The enumerator of the <see cref="T:System.Collections.Generic.IEnumerable`1" /> source. + + + Represent the collection of form data. + + + Initializes a new instance of class. + The pairs. + + + Initializes a new instance of class. + The query. + + + Initializes a new instance of class. + The URI + + + Gets the collection of form data. + The collection of form data. + The key. + + + Gets an enumerable that iterates through the collection. + The enumerable that iterates through the collection. + + + Gets the values of the collection of form data. + The values of the collection of form data. + The key. + + + Gets values associated with a given key. If there are multiple values, they're concatenated. + Values associated with a given key. If there are multiple values, they're concatenated. + + + Reads the collection of form data as a collection of name value. + The collection of form data as a collection of name value. + + + Gets an enumerable that iterates through the collection. + The enumerable that iterates through the collection. + + + + class for handling HTML form URL-ended data, also known as application/x-www-form-urlencoded. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The instance to copy settings from. + + + Queries whether the can deserializean object of the specified type. + true if the can deserialize the type; otherwise, false. + The type to deserialize. + + + Queries whether the can serializean object of the specified type. + true if the can serialize the type; otherwise, false. + The type to serialize. + + + Gets the default media type for HTML form-URL-encoded data, which is application/x-www-form-urlencoded. + The default media type for HTML form-URL-encoded data + + + Gets or sets the maximum depth allowed by this formatter. + The maximum depth. + + + Gets or sets the size of the buffer when reading the incoming stream. + The buffer size. + + + Asynchronously deserializes an object of the specified type. + A whose result will be the object instance that has been read. + The type of object to deserialize. + The to read. + The for the content being read. + The to log events to. + + + Performs content negotiation. This is the process of selecting a response writer (formatter) in compliance with header values in the request. + + + Performs content negotiating by selecting the most appropriate out of the passed in formatters for the given request that can serialize an object of the given type. + The result of the negotiation containing the most appropriate instance, or null if there is no appropriate formatter. + The type to be serialized. + Request message, which contains the header values used to perform negotiation. + The set of objects from which to choose. + + + Specifies a callback interface that a formatter can use to log errors while reading. + + + Logs an error. + The path to the member for which the error is being logged. + The error message. + + + Logs an error. + The path to the member for which the error is being logged. + The error message to be logged. + + + Defines method that determines whether a given member is required on deserialization. + + + Determines whether a given member is required on deserialization. + true if should be treated as a required member; otherwise false. + The to be deserialized. + + + Represents the default used by . It uses the formatter's to select required members and recognizes the type annotation. + + + Initializes a new instance of the class. + The formatter to use for resolving required members. + + + Creates a property on the specified class by using the specified parameters. + A to create on the specified class by using the specified parameters. + The member info. + The member serialization. + + + Represents the class to handle JSON. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The instance to copy settings from. + + + Determines whether this can read objects of the specified . + true if objects of this can be read, otherwise false. + The type of object that will be read. + + + Determines whether this can write objects of the specified . + true if objects of this can be written, otherwise false. + The type of object that will be written. + + + Called during deserialization to get the . + The object used for serialization. + The type of object that will be serialized or deserialized. + + + Called during deserialization to get the . + The reader to use during deserialization. + The type of the object to read. + The stream from which to read. + The encoding to use when reading. + + + Called during serialization to get the . + The writer to use during serialization. + The type of the object to write. + The stream to write to. + The encoding to use when writing. + + + Gets the default media type for JSON, namely "application/json". + The for JSON. + + + Gets or sets a value indicating whether to indent elements when writing data. + true if to indent elements when writing data; otherwise, false. + + + Gets or sets the maximum depth allowed by this formatter. + The maximum depth allowed by this formatter. + + + Called during deserialization to read an object of the specified type from the specified stream. + The object that has been read. + The type of the object to read. + The stream from which to read. + The encoding to use when reading. + The logger to log events to. + + + Gets or sets a value indicating whether to use by default. + true if to by default; otherwise, false. + + + Called during serialization to write an object of the specified type to the specified stream. + The type of the object to write. + The object to write. + The stream to write to. + The encoding to use when writing. + + + Called during serialization to write an object of the specified type to the specified stream. + Returns . + The type of the object to write. + The object to write. + The stream to write to. + The for the content being written. + The transport context. + The token to monitor for cancellation. + + + Base class to handle serializing and deserializing strongly-typed objects using . + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The instance to copy settings from. + + + Queries whether this can deserializean object of the specified type. + true if the can deserialize the type; otherwise, false. + The type to deserialize. + + + Queries whether this can serializean object of the specified type. + true if the can serialize the type; otherwise, false. + The type to serialize. + + + Gets the default value for the specified type. + The default value. + The type for which to get the default value. + + + Returns a specialized instance of the that can format a response for the given parameters. + Returns . + The type to format. + The request. + The media type. + + + Gets or sets the maximum number of keys stored in a T: . + The maximum number of keys. + + + Gets the mutable collection of objects that match HTTP requests to media types. + The collection. + + + Asynchronously deserializes an object of the specified type. + A whose result will be an object of the given type. + The type of the object to deserialize. + The to read. + The , if available. It may be null. + The to log events to. + Derived types need to support reading. + + + Asynchronously deserializes an object of the specified type. + A whose result will be an object of the given type. + The type of the object to deserialize. + The to read. + The , if available. It may be null. + The to log events to. + The token to cancel the operation. + + + Gets or sets the instance used to determine required members. + The instance. + + + Determines the best character encoding for reading or writing an HTTP entity body, given a set of content headers. + The encoding that is the best match. + The content headers. + + + Sets the default headers for content that will be formatted using this formatter. This method is called from the constructor. This implementation sets the Content-Type header to the value of mediaType if it is not null. If it is null it sets the Content-Type to the default media type of this formatter. If the Content-Type does not specify a charset it will set it using this formatters configured . + The type of the object being serialized. See . + The content headers that should be configured. + The authoritative media type. Can be null. + + + Gets the mutable collection of character encodings supported bythis . + The collection of objects. + + + Gets the mutable collection of media types supported bythis . + The collection of objects. + + + Asynchronously writes an object of the specified type. + A that will perform the write. + The type of the object to write. + The object value to write. It may be null. + The to which to write. + The if available. It may be null. + The if available. It may be null. + Derived types need to support writing. + + + Asynchronously writes an object of the specified type. + A that will perform the write. + The type of the object to write. + The object value to write. It may be null. + The to which to write. + The if available. It may be null. + The if available. It may be null. + The token to cancel the operation. + Derived types need to support writing. + + + Collection class that contains instances. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + A collection of instances to place in the collection. + + + Adds the elements of the specified collection to the end of the . + The items that should be added to the end of the . The items collection itself cannot be , but it can contain elements that are . + + + Removes all items in the collection. + + + Helper to search a collection for a formatter that can read the .NET type in the given mediaType. + The formatter that can read the type. Null if no formatter found. + The .NET type to read + The media type to match on. + + + Helper to search a collection for a formatter that can write the .NET type in the given mediaType. + The formatter that can write the type. Null if no formatter found. + The .NET type to read + The media type to match on. + + + Gets the to use for application/x-www-form-urlencoded data. + The to use for application/x-www-form-urlencoded data. + + + Inserts the specified item at the specified index in the collection. + The index to insert at. + The item to insert. + + + Inserts the elements of a collection into the at the specified index. + The zero-based index at which the new elements should be inserted. + The items that should be inserted into the . The items collection itself cannot be , but it can contain elements that are . + + + Returns true if the type is one of those loosely defined types that should be excluded from validation. + true if the type should be excluded; otherwise, false. + The .NET to validate. + + + Gets the to use for JSON. + The to use for JSON. + + + Removes the item at the specified index. + The index of the item to remove. + + + Assigns the item at the specified index in the collection. + The index to insert at. + The item to assign. + + + Gets the to use for XML. + The to use for XML. + + + + + + + This class describes how well a particular matches a request. + + + Initializes a new instance of the class. + The matching formatter. + The media type. Can be null in which case the media type application/octet-stream is used. + The quality of the match. Can be null in which case it is considered a full match with a value of 1.0 + The kind of match. + + + Gets the media type formatter. + + + Gets the matched media type. + + + Gets the quality of the match + + + Gets the kind of match that occurred. + + + Contains information about the degree to which a matches the explicit or implicit preferences found in an incoming request. + + + Matched on a type, meaning that the formatter is able to serialize the type. + + + Matched on an explicit “*/*” range in the Accept header. + + + Matched on an explicit literal accept header, such as “application/json”. + + + Matched on an explicit subtype range in an Accept header, such as “application/*”. + + + Matched on the media type of the entity body in the HTTP request message. + + + Matched on after having applied the various s. + + + No match was found + + + An abstract base class used to create an association between or instances that have certain characteristics and a specific . + + + Initializes a new instance of a with the given mediaType value. + The that is associated with or instances that have the given characteristics of the . + + + Initializes a new instance of a with the given mediaType value. + The that is associated with or instances that have the given characteristics of the . + + + Gets the that is associated with or instances that have the given characteristics of the . + + + Returns the quality of the match of the associated with request. + The quality of the match. It must be between 0.0 and 1.0. A value of 0.0 signifies no match. A value of 1.0 signifies a complete match. + The to evaluate for the characteristics associated with the of the . + + + Class that provides s from query strings. + + + Initializes a new instance of the class. + The name of the query string parameter to match, if present. + The value of the query string parameter specified by queryStringParameterName. + The to use if the query parameter specified by queryStringParameterName is present and assigned the value specified by queryStringParameterValue. + + + Initializes a new instance of the class. + The name of the query string parameter to match, if present. + The value of the query string parameter specified by queryStringParameterName. + The media type to use if the query parameter specified by queryStringParameterName is present and assigned the value specified by queryStringParameterValue. + + + Gets the query string parameter name. + + + Gets the query string parameter value. + + + Returns a value indicating whether the current instance can return a from request. + If this instance can produce a from request it returns 1.0 otherwise 0.0. + The to check. + + + This class provides a mapping from an arbitrary HTTP request header field to a used to select instances for handling the entity body of an or . <remarks>This class only checks header fields associated with for a match. It does not check header fields associated with or instances.</remarks> + + + Initializes a new instance of the class. + Name of the header to match. + The header value to match. + The to use when matching headerValue. + if set to true then headerValue is considered a match if it matches a substring of the actual header value. + The to use if headerName and headerValue is considered a match. + + + Initializes a new instance of the class. + Name of the header to match. + The header value to match. + The value comparison to use when matching headerValue. + if set to true then headerValue is considered a match if it matches a substring of the actual header value. + The media type to use if headerName and headerValue is considered a match. + + + Gets the name of the header to match. + + + Gets the header value to match. + + + Gets the to use when matching . + + + Gets a value indicating whether is a matched as a substring of the actual header value. this instance is value substring. + truefalse + + + Returns a value indicating whether the current instance can return a from request. + The quality of the match. It must be between 0.0 and 1.0. A value of 0.0 signifies no match. A value of 1.0 signifies a complete match. + The to check. + + + A that maps the X-Requested-With http header field set by AJAX XmlHttpRequest (XHR) to the media type application/json if no explicit Accept header fields are present in the request. + + + Initializes a new instance of class + + + Returns a value indicating whether the current instance can return a from request. + The quality of the match. A value of 0.0 signifies no match. A value of 1.0 signifies a complete match and that the request was made using XmlHttpRequest without an Accept header. + The to check. + + + + class to handle Xml. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The instance to copy settings from. + + + Queries whether the can deserializean object of the specified type. + true if the can deserialize the type; otherwise, false. + The type to deserialize. + + + Queries whether the can serializean object of the specified type. + true if the can serialize the type; otherwise, false. + The type to serialize. + + + Called during deserialization to get the DataContractSerializer serializer. + The object used for serialization. + The type of object that will be serialized or deserialized. + + + Called during deserialization to get the XML reader to use for reading objects from the stream. + The to use for reading objects. + The to read from. + The for the content being read. + + + Called during deserialization to get the XML serializer. + The object used for serialization. + The type of object that will be serialized or deserialized. + + + Called during serialization to get the XML writer to use for writing objects to the stream. + The to use for writing objects. + The to write to. + The for the content being written. + + + Gets the default media type for the XML formatter. + The default media type, which is “application/xml”. + + + Called during deserialization to get the XML serializer to use for deserializing objects. + An instance of or to use for deserializing the object. + The type of object to deserialize. + The for the content being read. + + + Called during serialization to get the XML serializer to use for serializing objects. + An instance of or to use for serializing the object. + The type of object to serialize. + The object to serialize. + The for the content being written. + + + Gets or sets a value indicating whether to indent elements when writing data. + true to indent elements; otherwise, false. + + + This method is to support infrastructure and is not intended to be used directly from your code. + Returns . + + + This method is to support infrastructure and is not intended to be used directly from your code. + Returns . + + + This method is to support infrastructure and is not intended to be used directly from your code. + Returns . + + + This method is to support infrastructure and is not intended to be used directly from your code. + Returns . + + + Gets and sets the maximum nested node depth. + The maximum nested node depth. + + + Called during deserialization to read an object of the specified type from the specified readStream. + A whose result will be the object instance that has been read. + The type of object to read. + The from which to read. + The for the content being read. + The to log events to. + + + Unregisters the serializer currently associated with the given type. + true if a serializer was previously registered for the type; otherwise, false. + The type of object whose serializer should be removed. + + + Registers an to read or write objects of a specified type. + The instance. + The type of object that will be serialized or deserialized with. + + + Registers an to read or write objects of a specified type. + The type of object that will be serialized or deserialized with. + The instance. + + + Registers an to read or write objects of a specified type. + The type of object that will be serialized or deserialized with. + The instance. + + + Registers an to read or write objects of a specified type. + The instance. + The type of object that will be serialized or deserialized with. + + + Gets or sets a value indicating whether the XML formatter uses the as the default serializer, instead of using the . + If true, the formatter uses the by default; otherwise, it uses the by default. + + + Gets the settings to be used while writing. + The settings to be used while writing. + + + Called during serialization to write an object of the specified type to the specified writeStream. + A that will write the value to the stream. + The type of object to write. + The object to write. + The to which to write. + The for the content being written. + The . + The token to monitor cancellation. + + + Represents the event arguments for the HTTP progress. + + + Initializes a new instance of the class. + The percentage of the progress. + The user token. + The number of bytes transferred. + The total number of bytes transferred. + + + + + Generates progress notification for both request entities being uploaded and response entities being downloaded. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The inner message handler. + + + Occurs when event entities are being downloaded. + + + Occurs when event entities are being uploaded. + + + Raises the event that handles the request of the progress. + The request. + The event handler for the request. + + + Raises the event that handles the response of the progress. + The request. + The event handler for the request. + + + Sends the specified progress message to an HTTP server for delivery. + The sent progress message. + The request. + The cancellation token. + + + Provides value for the cookie header. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The value of the name. + The values. + + + Initializes a new instance of the class. + The value of the name. + The value. + + + Creates a shallow copy of the cookie value. + A shallow copy of the cookie value. + + + Gets a collection of cookies sent by the client. + A collection object representing the client’s cookie variables. + + + Gets or sets the domain to associate the cookie with. + The name of the domain to associate the cookie with. + + + Gets or sets the expiration date and time for the cookie. + The time of day (on the client) at which the cookie expires. + + + Gets or sets a value that specifies whether a cookie is accessible by client-side script. + true if the cookie has the HttpOnly attribute and cannot be accessed through a client-side script; otherwise, false. + + + Gets a shortcut to the cookie property. + The cookie value. + + + Gets or sets the maximum age permitted for a resource. + The maximum age permitted for a resource. + + + Gets or sets the virtual path to transmit with the current cookie. + The virtual path to transmit with the cookie. + + + Gets or sets a value indicating whether to transmit the cookie using Secure Sockets Layer (SSL)—that is, over HTTPS only. + true to transmit the cookie over an SSL connection (HTTPS); otherwise, false. + + + Returns a string that represents the current object. + A string that represents the current object. + + + Indicates a value whether the string representation will be converted. + true if the string representation will be converted; otherwise, false. + The input value. + The parsed value to convert. + + + Contains cookie name and its associated cookie state. + + + Initializes a new instance of the class. + The name of the cookie. + + + Initializes a new instance of the class. + The name of the cookie. + The collection of name-value pair for the cookie. + + + Initializes a new instance of the class. + The name of the cookie. + The value of the cookie. + + + Returns a new object that is a copy of the current instance. + A new object that is a copy of the current instance. + + + Gets or sets the cookie value with the specified cookie name, if the cookie data is structured. + The cookie value with the specified cookie name. + + + Gets or sets the name of the cookie. + The name of the cookie. + + + Returns the string representation the current object. + The string representation the current object. + + + Gets or sets the cookie value, if cookie data is a simple string value. + The value of the cookie. + + + Gets or sets the collection of name-value pair, if the cookie data is structured. + The collection of name-value pair for the cookie. + + + \ No newline at end of file diff --git a/WizBot/bin/Debug/System.Web.Http.xml b/WizBot/bin/Debug/System.Web.Http.xml new file mode 100644 index 000000000..365dd7b93 --- /dev/null +++ b/WizBot/bin/Debug/System.Web.Http.xml @@ -0,0 +1,6664 @@ + + + + System.Web.Http + + + + + Creates an that represents an exception. + The request must be associated with an instance.An whose content is a serialized representation of an instance. + The HTTP request. + The status code of the response. + The exception. + + + Creates an that represents an error message. + The request must be associated with an instance.An whose content is a serialized representation of an instance. + The HTTP request. + The status code of the response. + The error message. + + + Creates an that represents an exception with an error message. + The request must be associated with an instance.An whose content is a serialized representation of an instance. + The HTTP request. + The status code of the response. + The error message. + The exception. + + + Creates an that represents an error. + The request must be associated with an instance.An whose content is a serialized representation of an instance. + The HTTP request. + The status code of the response. + The HTTP error. + + + Creates an that represents an error in the model state. + The request must be associated with an instance.An whose content is a serialized representation of an instance. + The HTTP request. + The status code of the response. + The model state. + + + Creates an wired up to the associated . + An initialized wired up to the associated . + The HTTP request message which led to this response message. + The HTTP response status code. + The content of the HTTP response message. + The type of the HTTP response message. + + + Creates an wired up to the associated . + An initialized wired up to the associated . + The HTTP request message which led to this response message. + The HTTP response status code. + The content of the HTTP response message. + The media type formatter. + The type of the HTTP response message. + + + Creates an wired up to the associated . + An initialized wired up to the associated . + The HTTP request message which led to this response message. + The HTTP response status code. + The content of the HTTP response message. + The media type formatter. + The media type header value. + The type of the HTTP response message. + + + Creates an wired up to the associated . + An initialized wired up to the associated . + The HTTP request message which led to this response message. + The HTTP response status code. + The content of the HTTP response message. + The media type formatter. + The media type. + The type of the HTTP response message. + + + Creates an wired up to the associated . + An initialized wired up to the associated . + The HTTP request message which led to this response message. + The HTTP response status code. + The content of the HTTP response message. + The media type header value. + The type of the HTTP response message. + + + Creates an wired up to the associated . + An initialized wired up to the associated . + The HTTP request message which led to this response message. + The HTTP response status code. + The content of the HTTP response message. + The media type. + The type of the HTTP response message. + + + Creates an wired up to the associated . + An initialized wired up to the associated . + The HTTP request message which led to this response message. + The HTTP response status code. + The content of the HTTP response message. + The HTTP configuration which contains the dependency resolver used to resolve services. + The type of the HTTP response message. + + + + + + Disposes of all tracked resources associated with the which were added via the method. + The HTTP request. + + + + Gets the current X.509 certificate from the given HTTP request. + The current , or null if a certificate is not available. + The HTTP request. + + + Retrieves the for the given request. + The for the given request. + The HTTP request. + + + Retrieves the which has been assigned as the correlation ID associated with the given . The value will be created and set the first time this method is called. + The object that represents the correlation ID associated with the request. + The HTTP request. + + + Retrieves the for the given request or null if not available. + The for the given request or null if not available. + The HTTP request. + + + Gets the parsed query string as a collection of key-value pairs. + The query string as a collection of key-value pairs. + The HTTP request. + + + + + Retrieves the for the given request or null if not available. + The for the given request or null if not available. + The HTTP request. + + + Retrieves the for the given request or null if not available. + The for the given request or null if not available. + The HTTP request. + + + Gets a instance for an HTTP request. + A instance that is initialized for the specified HTTP request. + The HTTP request. + + + + + + Adds the given to a list of resources that will be disposed by a host once the is disposed. + The HTTP request controlling the lifecycle of . + The resource to dispose when is being disposed. + + + + + + + Represents the message extensions for the HTTP response from an ASP.NET operation. + + + Attempts to retrieve the value of the content for the . + The result of the retrieval of value of the content. + The response of the operation. + The value of the content. + The type of the value to retrieve. + + + Represents extensions for adding items to a . + + + + + Provides s from path extensions appearing in a . + + + Initializes a new instance of the class. + The extension corresponding to mediaType. This value should not include a dot or wildcards. + The that will be returned if uriPathExtension is matched. + + + Initializes a new instance of the class. + The extension corresponding to mediaType. This value should not include a dot or wildcards. + The media type that will be returned if uriPathExtension is matched. + + + Returns a value indicating whether this instance can provide a for the of request. + If this instance can match a file extension in request it returns 1.0 otherwise 0.0. + The to check. + + + Gets the path extension. + The path extension. + + + The path extension key. + + + Represents an attribute that specifies which HTTP methods an action method will respond to. + + + Initializes a new instance of the class by using the action method it will respond to. + The HTTP method that the action method will respond to. + + + Initializes a new instance of the class by using a list of HTTP methods that the action method will respond to. + The HTTP methods that the action method will respond to. + + + Gets or sets the list of HTTP methods that the action method will respond to. + Gets or sets the list of HTTP methods that the action method will respond to. + + + Represents an attribute that is used for the name of an action. + + + Initializes a new instance of the class. + The name of the action. + + + Gets or sets the name of the action. + The name of the action. + + + Specifies that actions and controllers are skipped by during authorization. + + + Initializes a new instance of the class. + + + Defines properties and methods for API controller. + + + + Gets the action context. + The action context. + + + Creates a . + A . + + + Creates an (400 Bad Request) with the specified error message. + An with the specified model state. + The user-visible error message. + + + Creates an with the specified model state. + An with the specified model state. + The model state to include in the error. + + + Gets the of the current . + The of the current . + + + Creates a (409 Conflict). + A . + + + Creates a <see cref="T:System.Web.Http.NegotiatedContentResult`1" /> with the specified values. + A <see cref="T:System.Web.Http.NegotiatedContentResult`1" /> with the specified values. + The HTTP status code for the response message. + The content value to negotiate and format in the entity body. + The type of content in the entity body. + + + Creates a <see cref="T:System.Web.Http.FormattedContentResult`1" /> with the specified values. + A <see cref="T:System.Web.Http.FormattedContentResult`1" /> with the specified values. + The HTTP status code for the response message. + The content value to format in the entity body. + The formatter to use to format the content. + The type of content in the entity body. + + + Creates a <see cref="T:System.Web.Http.FormattedContentResult`1" /> with the specified values. + A <see cref="T:System.Web.Http.FormattedContentResult`1" /> with the specified values. + The HTTP status code for the response message. + The content value to format in the entity body. + The formatter to use to format the content. + The value for the Content-Type header, or <see langword="null" /> to have the formatter pick a default value. + The type of content in the entity body. + + + Creates a <see cref="T:System.Web.Http.FormattedContentResult`1" /> with the specified values. + A <see cref="T:System.Web.Http.FormattedContentResult`1" /> with the specified values. + The HTTP status code for the response message. + The content value to format in the entity body. + The formatter to use to format the content. + The value for the Content-Type header. + The type of content in the entity body. + + + Gets the of the current . + The of the current . + + + Creates a (201 Created) with the specified values. + A with the specified values. + The location at which the content has been created. + The content value to negotiate and format in the entity body. + The type of content in the entity body. + + + Creates a (201 Created) with the specified values. + A with the specified values. + The location at which the content has been created. + The content value to negotiate and format in the entity body. + The type of content in the entity body. + + + Creates a (201 Created) with the specified values. + A with the specified values. + The name of the route to use for generating the URL. + The route data to use for generating the URL. + The content value to negotiate and format in the entity body. + The type of content in the entity body. + + + Creates a (201 Created) with the specified values. + A with the specified values. + The name of the route to use for generating the URL. + The route data to use for generating the URL. + The content value to negotiate and format in the entity body. + The type of content in the entity body. + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + Releases the unmanaged resources that are used by the object and, optionally, releases the managed resources. + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Executes asynchronously a single HTTP operation. + The newly started task. + The controller context for a single HTTP operation. + The cancellation token assigned for the HTTP operation. + + + Initializes the instance with the specified controllerContext. + The object that is used for the initialization. + + + Creates an (500 Internal Server Error). + A . + + + Creates an (500 Internal Server Error) with the specified exception. + An with the specified exception. + The exception to include in the error. + + + Creates a (200 OK) with the specified value. + A with the specified value. + The content value to serialize in the entity body. + The type of content in the entity body. + + + Creates a (200 OK) with the specified values. + A with the specified values. + The content value to serialize in the entity body. + The serializer settings. + The type of content in the entity body. + + + Creates a (200 OK) with the specified values. + A with the specified values. + The content value to serialize in the entity body. + The serializer settings. + The content encoding. + The type of content in the entity body. + + + Gets the model state after the model binding process. + The model state after the model binding process. + + + Creates a . + A . + + + Creates an (200 OK). + An . + + + Creates an with the specified values. + An with the specified values. + The content value to negotiate and format in the entity body. + The type of content in the entity body. + + + Creates a redirect result (302 Found) with the specified value. + A redirect result (302 Found) with the specified value. + The location to redirect to. + + + Creates a redirect result (302 Found) with the specified value. + A redirect result (302 Found) with the specified value. + The location to redirect to. + + + Creates a redirect to route result (302 Found) with the specified values. + A redirect to route result (302 Found) with the specified values. + The name of the route to use for generating the URL. + The route data to use for generating the URL. + + + Creates a redirect to route result (302 Found) with the specified values. + A redirect to route result (302 Found) with the specified values. + The name of the route to use for generating the URL. + The route data to use for generating the URL. + + + Gets or sets the HttpRequestMessage of the current . + The HttpRequestMessage of the current . + + + Gets the request context. + The request context. + + + Creates a with the specified response. + A for the specified response. + The HTTP response message. + + + Creates a with the specified status code. + A with the specified status code. + The HTTP status code for the response message + + + Creates an (401 Unauthorized) with the specified values. + An with the specified values. + The WWW-Authenticate challenges. + + + Creates an (401 Unauthorized) with the specified values. + An with the specified values. + The WWW-Authenticate challenges. + + + Gets an instance of a , which is used to generate URLs to other APIs. + A , which is used to generate URLs to other APIs. + + + Returns the current principal associated with this request. + The current principal associated with this request. + + + Validates the given entity and adds the validation errors to the model state under the empty prefix, if any. + The entity being validated. + The type of the entity to be validated. + + + Validates the given entity and adds the validation errors to the model state, if any. + The entity being validated. + The key prefix under which the model state errors would be added in the model state. + The type of the entity to be validated. + + + Specifies the authorization filter that verifies the request's . + + + Initializes a new instance of the class. + + + Processes requests that fail authorization. + The context. + + + Indicates whether the specified control is authorized. + true if the control is authorized; otherwise, false. + The context. + + + Calls when an action is being authorized. + The context. + The context parameter is null. + + + Gets or sets the authorized roles. + The roles string. + + + Gets a unique identifier for this attribute. + A unique identifier for this attribute. + + + Gets or sets the authorized users. + The users string. + + + An attribute that specifies that an action parameter comes only from the entity body of the incoming . + + + Initializes a new instance of the class. + + + Gets a parameter binding. + The parameter binding. + The parameter description. + + + An attribute that specifies that an action parameter comes from the URI of the incoming . + + + Initializes a new instance of the class. + + + Gets the value provider factories for the model binder. + A collection of objects. + The configuration. + + + Represents attributes that specifies that HTTP binding should exclude a property. + + + Initializes a new instance of the class. + + + Represents the required attribute for http binding. + + + Initializes a new instance of the class. + + + Represents a configuration of instances. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class with an HTTP route collection. + The HTTP route collection to associate with this instance. + + + Gets or sets the dependency resolver associated with thisinstance. + The dependency resolver. + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + Releases the unmanaged resources that are used by the object and, optionally, releases the managed resources. + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Invoke the Intializer hook. It is considered immutable from this point forward. It's safe to call this multiple times. + + + Gets the list of filters that apply to all requests served using this instance. + The list of filters. + + + Gets the media-type formatters for this instance. + A collection of objects. + + + Gets or sets a value indicating whether error details should be included in error messages. + The value that indicates that error detail policy. + + + Gets or sets the action that will perform final initialization of the instance before it is used to process requests. + The action that will perform final initialization of the instance. + + + Gets an ordered list of instances to be invoked as an travels up the stack and an travels down in stack in return. + The message handler collection. + + + Gets the collection of rules for how parameters should be bound. + A collection of functions that can produce a parameter binding for a given parameter. + + + Gets the properties associated with this instance. + The that contains the properties. + + + Gets the associated with this instance. + The . + + + Gets the container of default services associated with this instance. + The that contains the default services for this instance. + + + Gets the root virtual path. + The root virtual path. + + + Contains extension methods for the class. + + + + + Maps the attribute-defined routes for the application. + The server configuration. + The to use for discovering and building routes. + + + Maps the attribute-defined routes for the application. + The server configuration. + The constraint resolver. + + + Maps the attribute-defined routes for the application. + The server configuration. + The to use for resolving inline constraints. + The to use for discovering and building routes. + + + + Specifies that an action supports the DELETE HTTP method. + + + Initializes a new instance of the class. + + + Gets the http methods that correspond to this attribute. + The http methods that correspond to this attribute. + + + Defines a serializable container for storing error information. This information is stored as key/value pairs. The dictionary keys to look up standard error information are available on the type. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class for . + The exception to use for error information. + true to include the exception information in the error; false otherwise + + + Initializes a new instance of the class containing error message . + The error message to associate with this instance. + + + Initializes a new instance of the class for . + The invalid model state to use for error information. + true to include exception messages in the error; false otherwise + + + Gets or sets the message of the if available. + The message of the if available. + + + Gets or sets the type of the if available. + The type of the if available. + + + Gets a particular property value from this error instance. + A particular property value from this error instance. + The name of the error property. + The type of the property. + + + Gets the inner associated with this instance if available. + The inner associated with this instance if available. + + + Gets or sets the high-level, user-visible message explaining the cause of the error. Information carried in this field should be considered public in that it will go over the wire regardless of the . As a result care should be taken not to disclose sensitive information about the server or the application. + The high-level, user-visible message explaining the cause of the error. Information carried in this field should be considered public in that it will go over the wire regardless of the . As a result care should be taken not to disclose sensitive information about the server or the application. + + + Gets or sets a detailed description of the error intended for the developer to understand exactly what failed. + A detailed description of the error intended for the developer to understand exactly what failed. + + + Gets the containing information about the errors that occurred during model binding. + The containing information about the errors that occurred during model binding. + + + Gets or sets the stack trace information associated with this instance if available. + The stack trace information associated with this instance if available. + + + This method is reserved and should not be used. + Always returns null. + + + Generates an instance from its XML representation. + The XmlReader stream from which the object is deserialized. + + + Converts an instance into its XML representation. + The XmlWriter stream to which the object is serialized. + + + Provides keys to look up error information stored in the dictionary. + + + Provides a key for the ErrorCode. + + + Provides a key for the ExceptionMessage. + + + Provides a key for the ExceptionType. + + + Provides a key for the InnerException. + + + Provides a key for the MessageDetail. + + + Provides a key for the Message. + + + Provides a key for the MessageLanguage. + + + Provides a key for the ModelState. + + + Provides a key for the StackTrace. + + + Specifies that an action supports the GET HTTP method. + + + Initializes a new instance of the class. + + + Gets the http methods that correspond to this attribute. + The http methods that correspond to this attribute. + + + Specifies that an action supports the HEAD HTTP method. + + + Initializes a new instance of the class. + + + Gets the http methods that correspond to this attribute. + The http methods that correspond to this attribute. + + + Represents an attribute that is used to restrict an HTTP method so that the method handles only HTTP OPTIONS requests. + + + Initializes a new instance of the class. + + + Gets the http methods that correspond to this attribute. + The http methods that correspond to this attribute. + + + Specifies that an action supports the PATCH HTTP method. + + + Initializes a new instance of the class. + + + Gets the http methods that correspond to this attribute. + The http methods that correspond to this attribute. + + + Specifies that an action supports the POST HTTP method. + + + Initializes a new instance of the class. + + + Gets the http methods that correspond to this attribute. + The http methods that correspond to this attribute. + + + Represents an attribute that is used to restrict an HTTP method so that the method handles only HTTP PUT requests. + + + Initializes a new instance of the class. + + + Gets the http methods that correspond to this attribute. + The http methods that correspond to this attribute. + + + An exception that allows for a given to be returned to the client. + + + Initializes a new instance of the class. + The HTTP response to return to the client. + + + Initializes a new instance of the class. + The status code of the response. + + + Gets the HTTP response to return to the client. + The that represents the HTTP response. + + + A collection of instances. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The virtual path root. + + + Adds an instance to the collection. + The name of the route. + The instance to add to the collection. + + + Removes all items from the collection. + + + Determines whether the collection contains a specific . + true if the is found in the collection; otherwise, false. + The object to locate in the collection. + + + Determines whether the collection contains an element with the specified key. + true if the collection contains an element with the key; otherwise, false. + The key to locate in the collection. + + + Copies the instances of the collection to an array, starting at a particular array index. + The array that is the destination of the elements copied from the collection. + The zero-based index in at which copying begins. + + + Copies the route names and instances of the collection to an array, starting at a particular array index. + The array that is the destination of the elements copied from the collection. + The zero-based index in at which copying begins. + + + Gets the number of items in the collection. + The number of items in the collection. + + + Creates an instance. + The new instance. + The route template. + An object that contains the default route parameters. + An object that contains the route constraints. + The route data tokens. + + + Creates an instance. + The new instance. + The route template. + An object that contains the default route parameters. + An object that contains the route constraints. + The route data tokens. + The message handler for the route. + + + Creates an instance. + The new instance. + The route template. + An object that contains the default route parameters. + An object that contains the route constraints. + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + Releases the unmanaged resources that are used by the object and, optionally, releases the managed resources. + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Returns an enumerator that iterates through the collection. + An that can be used to iterate through the collection. + + + Gets the route data for a specified HTTP request. + An instance that represents the route data. + The HTTP request. + + + Gets a virtual path. + An instance that represents the virtual path. + The HTTP request. + The route name. + The route values. + + + Inserts an instance into the collection. + The zero-based index at which should be inserted. + The route name. + The to insert. The value cannot be null. + + + Gets a value indicating whether the collection is read-only. + true if the collection is read-only; otherwise, false. + + + Gets or sets the element at the specified index. + The at the specified index. + The index. + + + Gets or sets the element with the specified route name. + The at the specified index. + The route name. + + + Called internally to get the enumerator for the collection. + An that can be used to iterate through the collection. + + + Removes an instance from the collection. + true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the collection. + The name of the route to remove. + + + Adds an item to the collection. + The object to add to the collection. + + + Removes the first occurrence of a specific object from the collection. + true if was successfully removed from the collection; otherwise, false. This method also returns false if is not found in the original collection. + The object to remove from the collection. + + + Returns an enumerator that iterates through the collection. + An object that can be used to iterate through the collection. + + + Gets the with the specified route name. + true if the collection contains an element with the specified name; otherwise, false. + The route name. + When this method returns, contains the instance, if the route name is found; otherwise, null. This parameter is passed uninitialized. + + + Validates that a constraint is valid for an created by a call to the method. + The route template. + The constraint name. + The constraint object. + + + Gets the virtual path root. + The virtual path root. + + + Extension methods for + + + Ignores the specified route. + Returns . + A collection of routes for the application. + The name of the route to ignore. + The route template for the route. + + + Ignores the specified route. + Returns . + A collection of routes for the application. + The name of the route to ignore. + The route template for the route. + A set of expressions that specify values for the route template. + + + Maps the specified route for handling HTTP batch requests. + A collection of routes for the application. + The name of the route to map. + The route template for the route. + The for handling batch requests. + + + Maps the specified route template. + A reference to the mapped route. + A collection of routes for the application. + The name of the route to map. + The route template for the route. + + + Maps the specified route template and sets default route values. + A reference to the mapped route. + A collection of routes for the application. + The name of the route to map. + The route template for the route. + An object that contains default route values. + + + Maps the specified route template and sets default route values and constraints. + A reference to the mapped route. + A collection of routes for the application. + The name of the route to map. + The route template for the route. + An object that contains default route values. + A set of expressions that specify values for . + + + Maps the specified route template and sets default route values, constraints, and end-point message handler. + A reference to the mapped route. + A collection of routes for the application. + The name of the route to map. + The route template for the route. + An object that contains default route values. + A set of expressions that specify values for . + The handler to which the request will be dispatched. + + + Defines an implementation of an which dispatches an incoming and creates an as a result. + + + Initializes a new instance of the class, using the default configuration and dispatcher. + + + Initializes a new instance of the class with a specified dispatcher. + The HTTP dispatcher that will handle incoming requests. + + + Initializes a new instance of the class with a specified configuration. + The used to configure this instance. + + + Initializes a new instance of the class with a specified configuration and dispatcher. + The used to configure this instance. + The HTTP dispatcher that will handle incoming requests. + + + Gets the used to configure this instance. + The used to configure this instance. + + + Gets the HTTP dispatcher that handles incoming requests. + The HTTP dispatcher that handles incoming requests. + + + Releases the unmanaged resources that are used by the object and, optionally, releases the managed resources. + true to release both managed and unmanaged resources; false to release only unmanaged resources. + + + Prepares the server for operation. + + + Dispatches an incoming . + A task representing the asynchronous operation. + The request to dispatch. + The token to monitor for cancellation requests. + + + Defines a command that asynchronously creates an . + + + Creates an asynchronously. + A task that, when completed, contains the . + The token to monitor for cancellation requests. + + + Specifies whether error details, such as exception messages and stack traces, should be included in error messages. + + + Always include error details. + + + Use the default behavior for the host environment. For ASP.NET hosting, use the value from the customErrors element in the Web.config file. For self-hosting, use the value . + + + Only include error details when responding to a local request. + + + Never include error details. + + + Represents an attribute that is used to indicate that a controller method is not an action method. + + + Initializes a new instance of the class. + + + Represents a filter attribute that overrides action filters defined at a higher level. + + + Initializes a new instance of the class. + + + Gets a value indicating whether the action filter allows multiple attribute. + true if the action filter allows multiple attribute; otherwise, false. + + + Gets the type of filters to override. + The type of filters to override. + + + Represents a filter attribute that overrides authentication filters defined at a higher level. + + + + + + Represents a filter attribute that overrides authorization filters defined at a higher level. + + + Initializes a new instance of the class. + + + Gets or sets a Boolean value indicating whether more than one instance of the indicated attribute can be specified for a single program element. + true if more than one instance is allowed to be specified; otherwise, false. + + + Gets the type to filters override attributes. + The type to filters override attributes. + + + Represents a filter attribute that overrides exception filters defined at a higher level. + + + + + + Attribute on a parameter or type that produces a . If the attribute is on a type-declaration, then it's as if that attribute is present on all action parameters of that type. + + + Initializes a new instance of the class. + + + Gets the parameter binding. + The parameter binding. + The parameter description. + + + Place on an action to expose it directly via a route. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The route template describing the URI pattern to match against. + + + Returns . + + + Returns . + + + + Returns . + + + The class can be used to indicate properties about a route parameter (the literals and placeholders located within segments of a ). It can for example be used to indicate that a route parameter is optional. + + + An optional parameter. + + + Returns a that represents this instance. + A that represents this instance. + + + Annotates a controller with a route prefix that applies to all actions within the controller. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The route prefix for the controller. + + + Gets the route prefix. + + + Provides type-safe accessors for services obtained from a object. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Returns the registered unhandled exception handler, if any. + The registered unhandled exception hander, if present; otherwise, null. + The services container. + + + Returns the collection of registered unhandled exception loggers. + The collection of registered unhandled exception loggers. + The services container. + + + Gets the collection. + Returns a collection of objects. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance, or null if no instance was registered. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the collection. + Returns a collection of objects. + The services container. + + + Gets the service. + Returns an instance. + The services container. + + + Gets the collection. + Returns a collection ofobjects. + The services container. + + + Gets the service. + Returns aninstance. + The services container. + + + Gets the service. + Returns aninstance. + The services container. + + + Gets the collection. + Returns a collection of objects. + The services container. + + + Represents an containing zero or one entities. Use together with an [EnableQuery] from the System.Web.Http.OData or System.Web.OData namespace. + + + Initializes a new instance of the class. + The containing zero or one entities. + + + Creates a from an . A helper method to instantiate a object without having to explicitly specify the type . + The created . + The containing zero or one entities. + The type of the data in the data source. + + + The containing zero or one entities. + + + Represents an containing zero or one entities. Use together with an [EnableQuery] from the System.Web.Http.OData or System.Web.OData namespace. + The type of the data in the data source. + + + Initializes a new instance of the class. + The containing zero or one entities. + + + The containing zero or one entities. + + + Defines the order of execution for batch requests. + + + Executes the batch requests non-sequentially. + + + Executes the batch requests sequentially. + + + Provides extension methods for the class. + + + Copies the properties from another . + The sub-request. + The batch request that contains the properties to copy. + + + Represents the default implementation of that encodes the HTTP request/response messages as MIME multipart. + + + Initializes a new instance of the class. + The for handling the individual batch requests. + + + Creates the batch response message. + The batch response message. + The responses for the batch requests. + The original request containing all the batch requests. + The cancellation token. + + + Executes the batch request messages. + A collection of for the batch requests. + The collection of batch request messages. + The cancellation token. + + + Gets or sets the execution order for the batch requests. The default execution order is sequential. + The execution order for the batch requests. The default execution order is sequential. + + + Converts the incoming batch request into a collection of request messages. + A collection of . + The request containing the batch request messages. + The cancellation token. + + + Processes the batch requests. + The result of the operation. + The batch request. + The cancellation token. + + + Gets the supported content types for the batch request. + The supported content types for the batch request. + + + Validates the incoming request that contains the batch request messages. + The request containing the batch request messages. + + + Defines the abstraction for handling HTTP batch requests. + + + Initializes a new instance of the class. + The for handling the individual batch requests. + + + Gets the invoker to send the batch requests to the . + The invoker to send the batch requests to the . + + + Processes the incoming batch request as a single . + The batch response. + The batch request. + The cancellation token. + + + Sends the batch handler asynchronously. + The result of the operation. + the send request. + The cancelation token. + + + Invokes the action methods of a controller. + + + Initializes a new instance of the class. + + + Asynchronously invokes the specified action by using the specified controller context. + The invoked action. + The controller context. + The cancellation token. + + + Represents a reflection based action selector. + + + Initializes a new instance of the class. + + + Gets the action mappings for the . + The action mappings. + The information that describes a controller. + + + Selects an action for the . + The selected action. + The controller context. + + + Represents a container for services that can be specific to a controller. This shadows the services from its parent . A controller can either set a service here, or fall through to the more global set of services. + + + Initializes a new instance of the class. + The parent services container. + + + Removes a single-instance service from the default services. + The type of service. + + + Gets a service of the specified type. + The first instance of the service, or null if the service is not found. + The type of service. + + + Gets the list of service objects for a given service type, and validates the service type. + The list of service objects of the specified type. + The service type. + + + Gets the list of service objects for a given service type. + The list of service objects of the specified type, or an empty list if the service is not found. + The type of service. + + + Queries whether a service type is single-instance. + true if the service type has at most one instance, or false if the service type supports multiple instances. + The service type. + + + Replaces a single-instance service object. + The service type. + The service object that replaces the previous instance. + + + Describes *how* the binding will happen and does not actually bind. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The back pointer to the action this binding is for. + The synchronous bindings for each parameter. + + + Gets or sets the back pointer to the action this binding is for. + The back pointer to the action this binding is for. + + + Executes asynchronously the binding for the given request context. + Task that is signaled when the binding is complete. + The action context for the binding. This contains the parameter dictionary that will get populated. + The cancellation token for cancelling the binding operation. Or a binder can also bind a parameter to this. + + + Gets or sets the synchronous bindings for each parameter. + The synchronous bindings for each parameter. + + + Contains information for the executing action. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The controller context. + The action descriptor. + + + Gets a list of action arguments. + A list of action arguments. + + + Gets or sets the action descriptor for the action context. + The action descriptor. + + + Gets or sets the controller context. + The controller context. + + + Gets the model state dictionary for the context. + The model state dictionary. + + + Gets the request message for the action context. + The request message for the action context. + + + Gets the current request context. + The current request context. + + + Gets or sets the response message for the action context. + The response message for the action context. + + + Contains extension methods for . + + + + + + + + + + + Provides information about the action methods. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class with specified information that describes the controller of the action.. + The information that describes the controller of the action. + + + Gets or sets the binding that describes the action. + The binding that describes the action. + + + Gets the name of the action. + The name of the action. + + + Gets or sets the action configuration. + The action configuration. + + + Gets the information that describes the controller of the action. + The information that describes the controller of the action. + + + Executes the described action and returns a that once completed will contain the return value of the action. + A that once completed will contain the return value of the action. + The controller context. + A list of arguments. + The cancellation token. + + + Returns the custom attributes associated with the action descriptor. + The custom attributes associated with the action descriptor. + The action descriptor. + + + Gets the custom attributes for the action. + The collection of custom attributes applied to this action. + true to search this action's inheritance chain to find the attributes; otherwise, false. + The type of attribute to search for. + + + Retrieves the filters for the given configuration and action. + The filters for the given configuration and action. + + + Retrieves the filters for the action descriptor. + The filters for the action descriptor. + + + Retrieves the parameters for the action descriptor. + The parameters for the action descriptor. + + + Gets the properties associated with this instance. + The properties associated with this instance. + + + Gets the converter for correctly transforming the result of calling ExecuteAsync(HttpControllerContext, IDictionaryString, Object)" into an instance of . + The action result converter. + + + Gets the return type of the descriptor. + The return type of the descriptor. + + + Gets the collection of supported HTTP methods for the descriptor. + The collection of supported HTTP methods for the descriptor. + + + Contains information for a single HTTP operation. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The request context. + The HTTP request. + The controller descriptor. + The controller. + + + Initializes a new instance of the class. + The configuration. + The route data. + The request. + + + Gets or sets the configuration. + The configuration. + + + Gets or sets the HTTP controller. + The HTTP controller. + + + Gets or sets the controller descriptor. + The controller descriptor. + + + Gets or sets the request. + The request. + + + Gets or sets the request context. + + + Gets or sets the route data. + The route data. + + + Represents information that describes the HTTP controller. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The configuration. + The controller name. + The controller type. + + + Gets or sets the configurations associated with the controller. + The configurations associated with the controller. + + + Gets or sets the name of the controller. + The name of the controller. + + + Gets or sets the type of the controller. + The type of the controller. + + + Creates a controller instance for the given . + The created controller instance. + The request message. + + + Retrieves a collection of custom attributes of the controller. + A collection of custom attributes. + The type of the object. + + + Returns a collection of attributes that can be assigned to <typeparamref name="T" /> for this descriptor's controller. + A collection of attributes associated with this controller. + true to search this controller's inheritance chain to find the attributes; otherwise, false. + Used to filter the collection of attributes. Use a value of to retrieve all attributes. + + + Returns a collection of filters associated with the controller. + A collection of filters associated with the controller. + + + Gets the properties associated with this instance. + The properties associated with this instance. + + + Contains settings for an HTTP controller. + + + Initializes a new instance of the class. + A configuration object that is used to initialize the instance. + + + Gets the collection of instances for the controller. + The collection of instances. + + + Gets the collection of parameter bindingfunctions for for the controller. + The collection of parameter binding functions. + + + Gets the collection of service instances for the controller. + The collection of service instances. + + + Describes how a parameter is bound. The binding should be static (based purely on the descriptor) and can be shared across requests. + + + Initializes a new instance of the class. + An that describes the parameters. + + + Gets the that was used to initialize this instance. + The instance. + + + If the binding is invalid, gets an error message that describes the binding error. + An error message. If the binding was successful, the value is null. + + + Asynchronously executes the binding for the given request. + A task object representing the asynchronous operation. + Metadata provider to use for validation. + The action context for the binding. The action context contains the parameter dictionary that will get populated with the parameter. + Cancellation token for cancelling the binding operation. + + + Gets the parameter value from argument dictionary of the action context. + The value for this parameter in the given action context, or null if the parameter has not yet been set. + The action context. + + + Gets a value that indicates whether the binding was successful. + true if the binding was successful; otherwise, false. + + + Sets the result of this parameter binding in the argument dictionary of the action context. + The action context. + The parameter value. + + + Returns a value indicating whether this instance will read the entity body of the HTTP message. + true if this will read the entity body; otherwise, false. + + + Represents the HTTP parameter descriptor. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The action descriptor. + + + Gets or sets the action descriptor. + The action descriptor. + + + Gets or sets the for the . + The for the . + + + Gets the default value of the parameter. + The default value of the parameter. + + + Retrieves a collection of the custom attributes from the parameter. + A collection of the custom attributes from the parameter. + The type of the custom attributes. + + + Gets a value that indicates whether the parameter is optional. + true if the parameter is optional; otherwise, false. + + + Gets or sets the parameter binding attribute. + The parameter binding attribute. + + + Gets the name of the parameter. + The name of the parameter. + + + Gets the type of the parameter. + The type of the parameter. + + + Gets the prefix of this parameter. + The prefix of this parameter. + + + Gets the properties of this parameter. + The properties of this parameter. + + + Represents the context associated with a request. + + + Initializes a new instance of the class. + + + Gets or sets the client certificate. + Returns . + + + Gets or sets the configuration. + Returns . + + + Gets or sets a value indicating whether error details, such as exception messages and stack traces, should be included in the response for this request. + Returns . + + + Gets or sets a value indicating whether the request originates from a local address. + Returns . + + + .Gets or sets the principal + Returns . + + + Gets or sets the route data. + Returns . + + + Gets or sets the factory used to generate URLs to other APIs. + Returns . + + + Gets or sets the virtual path root. + Returns . + + + + + A contract for a conversion routine that can take the result of an action returned from <see cref="M:System.Web.Http.Controllers.HttpActionDescriptor.ExecuteAsync(System.Web.Http.Controllers.HttpControllerContext,System.Collections.Generic.IDictionary{System.String,System.Object})" /> and convert it to an instance of . + + + Converts the specified object to another object. + The converted object. + The controller context. + The action result. + + + Defines the method for retrieval of action binding associated with parameter value. + + + Gets the . + A object. + The action descriptor. + + + If a controller is decorated with an attribute with this interface, then it gets invoked to initialize the controller settings. + + + Callback invoked to set per-controller overrides for this controllerDescriptor. + The controller settings to initialize. + The controller descriptor. Note that the can be associated with the derived controller type given that is inherited. + + + Contains method that is used to invoke HTTP operation. + + + Executes asynchronously the HTTP operation. + The newly started task. + The execution context. + The cancellation token assigned for the HTTP operation. + + + Contains the logic for selecting an action method. + + + Returns a map, keyed by action string, of all that the selector can select. This is primarily called by to discover all the possible actions in the controller. + A map of that the selector can select, or null if the selector does not have a well-defined mapping of . + The controller descriptor. + + + Selects the action for the controller. + The action for the controller. + The context of the controller. + + + Represents an HTTP controller. + + + Executes the controller for synchronization. + The controller. + The current context for a test controller. + The notification that cancels the operation. + + + Defines extension methods for . + + + Binds parameter that results as an error. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + The error message that describes the reason for fail bind. + + + Bind the parameter as if it had the given attribute on the declaration. + The HTTP parameter binding object. + The parameter to provide binding for. + The attribute that describes the binding. + + + Binds parameter by parsing the HTTP body content. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + + + Binds parameter by parsing the HTTP body content. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + The list of formatters which provides selection of an appropriate formatter for serializing the parameter into object. + + + Binds parameter by parsing the HTTP body content. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + The list of formatters which provides selection of an appropriate formatter for serializing the parameter into object. + The body model validator used to validate the parameter. + + + Binds parameter by parsing the HTTP body content. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + The list of formatters which provides selection of an appropriate formatter for serializing the parameter into object. + + + Binds parameter by parsing the query string. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + + + Binds parameter by parsing the query string. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + The value provider factories which provide query string parameter data. + + + Binds parameter by parsing the query string. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + The model binder used to assemble the parameter into an object. + + + Binds parameter by parsing the query string. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + The model binder used to assemble the parameter into an object. + The value provider factories which provide query string parameter data. + + + Binds parameter by parsing the query string. + The HTTP parameter binding object. + The parameter descriptor that describes the parameter to bind. + The value provider factories which provide query string parameter data. + + + Represents a reflected synchronous or asynchronous action method. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class with the specified descriptor and method details.. + The controller descriptor. + The action-method information. + + + Gets the name of the action. + The name of the action. + + + + Executes the described action and returns a that once completed will contain the return value of the action. + A [T:System.Threading.Tasks.Task`1"] that once completed will contain the return value of the action. + The context. + The arguments. + A cancellation token to cancel the action. + + + Returns an array of custom attributes defined for this member, identified by type. + An array of custom attributes or an empty array if no custom attributes exist. + true to search this action's inheritance chain to find the attributes; otherwise, false. + The type of the custom attributes. + + + Retrieves information about action filters. + The filter information. + + + + Retrieves the parameters of the action method. + The parameters of the action method. + + + Gets or sets the action-method information. + The action-method information. + + + Gets the return type of this method. + The return type of this method. + + + Gets or sets the supported http methods. + The supported http methods. + + + Represents the reflected HTTP parameter descriptor. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The action descriptor. + The parameter information. + + + Gets the default value for the parameter. + The default value for the parameter. + + + Retrieves a collection of the custom attributes from the parameter. + A collection of the custom attributes from the parameter. + The type of the custom attributes. + + + Gets a value that indicates whether the parameter is optional. + true if the parameter is optional; otherwise false. + + + Gets or sets the parameter information. + The parameter information. + + + Gets the name of the parameter. + The name of the parameter. + + + Gets the type of the parameter. + The type of the parameter. + + + Represents a converter for actions with a return type of . + + + Initializes a new instance of the class. + + + Converts a object to another object. + The converted object. + The controller context. + The action result. + + + An abstract class that provides a container for services used by ASP.NET Web API. + + + Initializes a new instance of the class. + + + Adds a service to the end of services list for the given service type. + The service type. + The service instance. + + + Adds the services of the specified collection to the end of the services list for the given service type. + The service type. + The services to add. + + + Removes all the service instances of the given service type. + The service type to clear from the services list. + + + Removes all instances of a multi-instance service type. + The service type to remove. + + + Removes a single-instance service type. + The service type to remove. + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + Searches for a service that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence. + The zero-based index of the first occurrence, if found; otherwise, -1. + The service type. + The delegate that defines the conditions of the element to search for. + + + Gets a service instance of a specified type. + The service type. + + + Gets a mutable list of service instances of a specified type. + A mutable list of service instances. + The service type. + + + Gets a collection of service instanes of a specified type. + A collection of service instances. + The service type. + + + Inserts a service into the collection at the specified index. + The service type. + The zero-based index at which the service should be inserted. If is passed, ensures the element is added to the end. + The service to insert. + + + Inserts the elements of the collection into the service list at the specified index. + The service type. + The zero-based index at which the new elements should be inserted. If is passed, ensures the elements are added to the end. + The collection of services to insert. + + + Determine whether the service type should be fetched with GetService or GetServices. + true iff the service is singular. + type of service to query + + + Removes the first occurrence of the given service from the service list for the given service type. + true if the item is successfully removed; otherwise, false. + The service type. + The service instance to remove. + + + Removes all the elements that match the conditions defined by the specified predicate. + The number of elements removed from the list. + The service type. + The delegate that defines the conditions of the elements to remove. + + + Removes the service at the specified index. + The service type. + The zero-based index of the service to remove. + + + Replaces all existing services for the given service type with the given service instance. This works for both singular and plural services. + The service type. + The service instance. + + + Replaces all instances of a multi-instance service with a new instance. + The type of service. + The service instance that will replace the current services of this type. + + + Replaces all existing services for the given service type with the given service instances. + The service type. + The service instances. + + + Replaces a single-instance service of a specified type. + The service type. + The service instance. + + + Removes the cached values for a single service type. + The service type. + + + A converter for creating responses from actions that return an arbitrary value. + The declared return type of an action. + + + Initializes a new instance of the class. + + + Converts the result of an action with arbitrary return type to an instance of . + The newly created object. + The action controller context. + The execution result. + + + Represents a converter for creating a response from actions that do not return a value. + + + Initializes a new instance of the class. + + + Converts the created response from actions that do not return a value. + The converted response. + The context of the controller. + The result of the action. + + + Represents a dependency injection container. + + + Starts a resolution scope. + The dependency scope. + + + Represents an interface for the range of the dependencies. + + + Retrieves a service from the scope. + The retrieved service. + The service to be retrieved. + + + Retrieves a collection of services from the scope. + The retrieved collection of services. + The collection of services to be retrieved. + + + Describes an API defined by relative URI path and HTTP method. + + + Initializes a new instance of the class. + + + Gets or sets the action descriptor that will handle the API. + The action descriptor. + + + Gets or sets the documentation of the API. + The documentation. + + + Gets or sets the HTTP method. + The HTTP method. + + + Gets the ID. The ID is unique within . + The ID. + + + Gets the parameter descriptions. + The parameter descriptions. + + + Gets or sets the relative path. + The relative path. + + + Gets or sets the response description. + The response description. + + + Gets or sets the registered route for the API. + The route. + + + Gets the supported request body formatters. + The supported request body formatters. + + + Gets the supported response formatters. + The supported response formatters. + + + Explores the URI space of the service based on routes, controllers and actions available in the system. + + + Initializes a new instance of the class. + The configuration. + + + Gets the API descriptions. The descriptions are initialized on the first access. + + + Gets or sets the documentation provider. The provider will be responsible for documenting the API. + The documentation provider. + + + Gets a collection of HttpMethods supported by the action. Called when initializing the . + A collection of HttpMethods supported by the action. + The route. + The action descriptor. + + + Determines whether the action should be considered for generation. Called when initializing the . + true if the action should be considered for generation, false otherwise. + The action variable value from the route. + The action descriptor. + The route. + + + Determines whether the controller should be considered for generation. Called when initializing the . + true if the controller should be considered for generation, false otherwise. + The controller variable value from the route. + The controller descriptor. + The route. + + + This attribute can be used on the controllers and actions to influence the behavior of . + + + Initializes a new instance of the class. + + + Gets or sets a value indicating whether to exclude the controller or action from the instances generated by . + true if the controller or action should be ignored; otherwise, false. + + + Describes a parameter on the API defined by relative URI path and HTTP method. + + + Initializes a new instance of the class. + + + Gets or sets the documentation. + The documentation. + + + Gets or sets the name. + The name. + + + Gets or sets the parameter descriptor. + The parameter descriptor. + + + Gets or sets the source of the parameter. It may come from the request URI, request body or other places. + The source. + + + Describes where the parameter come from. + + + The parameter come from Body. + + + The parameter come from Uri. + + + The location is unknown. + + + Defines the interface for getting a collection of . + + + Gets the API descriptions. + + + Defines the provider responsible for documenting the service. + + + Gets the documentation based on . + The documentation for the controller. + The action descriptor. + + + + Gets the documentation based on . + The documentation for the controller. + The parameter descriptor. + + + + Describes the API response. + + + Initializes a new instance of the class. + + + Gets or sets the declared response type. + The declared response type. + + + Gets or sets the response documentation. + The response documentation. + + + Gets or sets the actual response type. + The actual response type. + + + Use this to specify the entity type returned by an action when the declared return type is or . The will be read by when generating . + + + Initializes a new instance of the class. + The response type. + + + Gets the response type. + + + Provides an implementation of with no external dependencies. + + + Initializes a new instance of the class. + + + Returns a list of assemblies available for the application. + A <see cref="T:System.Collections.ObjectModel.Collection`1" /> of assemblies. + + + Represents a default implementation of an . A different implementation can be registered via the . We optimize for the case where we have an instance per instance but can support cases where there are many instances for one as well. In the latter case the lookup is slightly slower because it goes through the dictionary. + + + Initializes a new instance of the class. + + + Creates the specified by using the given . + An instance of type . + The request message. + The controller descriptor. + The type of the controller. + + + Represents a default instance for choosing a given a . A different implementation can be registered via the . + + + Initializes a new instance of the class. + The configuration. + + + Specifies the suffix string in the controller name. + + + Returns a map, keyed by controller string, of all that the selector can select. + A map of all that the selector can select, or null if the selector does not have a well-defined mapping of . + + + Gets the name of the controller for the specified . + The name of the controller for the specified . + The HTTP request message. + + + Selects a for the given . + The instance for the given . + The HTTP request message. + + + Provides an implementation of with no external dependencies. + + + Initializes a new instance of the class. + + + Initializes a new instance using a predicate to filter controller types. + The predicate. + + + Returns a list of controllers available for the application. + An <see cref="T:System.Collections.Generic.ICollection`1" /> of controllers. + The assemblies resolver. + + + Gets a value whether the resolver type is a controller type predicate. + true if the resolver type is a controller type predicate; otherwise, false. + + + Dispatches an incoming to an implementation for processing. + + + Initializes a new instance of the class with the specified configuration. + The http configuration. + + + Gets the HTTP configuration. + The HTTP configuration. + + + Dispatches an incoming to an . + A representing the ongoing operation. + The request to dispatch + The cancellation token. + + + This class is the default endpoint message handler which examines the of the matched route, and chooses which message handler to call. If is null, then it delegates to . + + + Initializes a new instance of the class, using the provided and as the default handler. + The server configuration. + + + Initializes a new instance of the class, using the provided and . + The server configuration. + The default handler to use when the has no . + + + Sends an HTTP request as an asynchronous operation. + The task object representing the asynchronous operation. + The HTTP request message to send. + The cancellation token to cancel operation. + + + Provides an abstraction for managing the assemblies of an application. A different implementation can be registered via the . + + + Returns a list of assemblies available for the application. + An <see cref="T:System.Collections.Generic.ICollection`1" /> of assemblies. + + + Defines the methods that are required for an . + + + Creates an object. + An object. + The message request. + The HTTP controller descriptor. + The type of the controller. + + + Defines the methods that are required for an factory. + + + Returns a map, keyed by controller string, of all that the selector can select. This is primarily called by to discover all the possible controllers in the system. + A map of all that the selector can select, or null if the selector does not have a well-defined mapping of . + + + Selects a for the given . + An instance. + The request message. + + + Provides an abstraction for managing the controller types of an application. A different implementation can be registered via the DependencyResolver. + + + Returns a list of controllers available for the application. + An <see cref="T:System.Collections.Generic.ICollection`1" /> of controllers. + The resolver for failed assemblies. + + + Provides the catch blocks used within this assembly. + + + Gets the catch block in System.Web.Http.ExceptionHandling.ExceptionCatchBlocks.HttpBatchHandler.SendAsync. + The catch block in System.Web.Http.ExceptionHandling.ExceptionCatchBlocks.HttpBatchHandler.SendAsync. + + + Gets the catch block in System.Web.Http.ExceptionHandling.ExceptionCatchBlocks.HttpControllerDispatcher.SendAsync. + The catch block in System.Web.Http.ExceptionHandling.ExceptionCatchBlocks.HttpControllerDispatcher.SendAsync. + + + Gets the catch block in System.Web.Http.ExceptionHandling.ExceptionCatchBlocks.HttpServer.SendAsync. + The catch block in System.Web.Http.ExceptionHandling.ExceptionCatchBlocks.HttpServer.SendAsync. + + + Gets the catch block in System.Web.Http.ApiController.ExecuteAsync when using . + The catch block in System.Web.Http.ApiController.ExecuteAsync when using . + + + Represents an exception and the contextual data associated with it when it was caught. + + + Initializes a new instance of the class. + The caught exception. + The catch block where the exception was caught. + + + Initializes a new instance of the class. + The caught exception. + The catch block where the exception was caught. + The request being processed when the exception was caught. + + + Initializes a new instance of the class. + The caught exception. + The catch block where the exception was caught. + The request being processed when the exception was caught. + The repsonse being returned when the exception was caught. + + + Initializes a new instance of the class. + The caught exception. + The catch block where the exception was caught. + The action context in which the exception occurred. + + + Gets the action context in which the exception occurred, if available. + The action context in which the exception occurred, if available. + + + Gets the catch block in which the exception was caught. + The catch block in which the exception was caught. + + + Gets the controller context in which the exception occurred, if available. + The controller context in which the exception occurred, if available. + + + Gets the caught exception. + The caught exception. + + + Gets the request being processed when the exception was caught. + The request being processed when the exception was caught. + + + Gets the request context in which the exception occurred. + The request context in which the exception occurred. + + + Gets the response being sent when the exception was caught. + The response being sent when the exception was caught. + + + Represents the catch block location for an exception context. + + + Initializes a new instance of the class. + The label for the catch block where the exception was caught. + A value indicating whether the catch block where the exception was caught is the last one before the host. + A value indicating whether exceptions in the catch block can be handled after they are logged. + + + Gets a value indicating whether exceptions in the catch block can be handled after they are logged. + A value indicating whether exceptions in the catch block can be handled after they are logged. + + + Gets a value indicating whether the catch block where the exception was caught is the last one before the host. + A value indicating whether the catch block where the exception was caught is the last one before the host. + + + Gets a label for the catch block in which the exception was caught. + A label for the catch block in which the exception was caught. + + + Returns . + + + Represents an unhandled exception handler. + + + Initializes a new instance of the class. + + + When overridden in a derived class, handles the exception synchronously. + The exception handler context. + + + When overridden in a derived class, handles the exception asynchronously. + A task representing the asynchronous exception handling operation. + The exception handler context. + The token to monitor for cancellation requests. + + + Determines whether the exception should be handled. + true if the exception should be handled; otherwise, false. + The exception handler context. + + + Returns . + + + Represents the context within which unhandled exception handling occurs. + + + Initializes a new instance of the class. + The exception context. + + + Gets the catch block in which the exception was caught. + The catch block in which the exception was caught. + + + Gets the caught exception. + The caught exception. + + + Gets the exception context providing the exception and related data. + The exception context providing the exception and related data. + + + Gets the request being processed when the exception was caught. + The request being processed when the exception was caught. + + + Gets the request context in which the exception occurred. + The request context in which the exception occurred. + + + Gets or sets the result providing the response message when the exception is handled. + The result providing the response message when the exception is handled. + + + Provides extension methods for . + + + Calls an exception handler and determines the response handling it, if any. + A task that, when completed, contains the response message to return when the exception is handled, or null when the exception remains unhandled. + The unhandled exception handler. + The exception context. + The token to monitor for cancellation requests. + + + Represents an unhandled exception logger. + + + Initializes a new instance of the class. + + + When overridden in a derived class, logs the exception synchronously. + The exception logger context. + + + When overridden in a derived class, logs the exception asynchronously. + A task representing the asynchronous exception logging operation. + The exception logger context. + The token to monitor for cancellation requests. + + + Determines whether the exception should be logged. + true if the exception should be logged; otherwise, false. + The exception logger context. + + + Returns . + + + Represents the context within which unhandled exception logging occurs. + + + Initializes a new instance of the class. + The exception context. + + + Gets or sets a value indicating whether the exception can subsequently be handled by an to produce a new response message. + A value indicating whether the exception can subsequently be handled by an to produce a new response message. + + + Gets the catch block in which the exception was caught. + The catch block in which the exception was caught. + + + Gets the caught exception. + The caught exception. + + + Gets the exception context providing the exception and related data. + The exception context providing the exception and related data. + + + Gets the request being processed when the exception was caught. + The request being processed when the exception was caught. + + + Gets the request context in which the exception occurred. + The request context in which the exception occurred. + + + Provides extension methods for . + + + Calls an exception logger. + A task representing the asynchronous exception logging operation. + The unhandled exception logger. + The exception context. + The token to monitor for cancellation requests. + + + Creates exception services to call logging and handling from catch blocks. + + + Gets an exception handler that calls the registered handler service, if any, and ensures exceptions do not accidentally propagate to the host. + An exception handler that calls any registered handler and ensures exceptions do not accidentally propagate to the host. + The services container. + + + Gets an exception handler that calls the registered handler service, if any, and ensures exceptions do not accidentally propagate to the host. + An exception handler that calls any registered handler and ensures exceptions do not accidentally propagate to the host. + The configuration. + + + Gets an exception logger that calls all registered logger services. + A composite logger. + The services container. + + + Gets an exception logger that calls all registered logger services. + A composite logger. + The configuration. + + + Defines an unhandled exception handler. + + + Process an unhandled exception, either allowing it to propagate or handling it by providing a response message to return instead. + A task representing the asynchronous exception handling operation. + The exception handler context. + The token to monitor for cancellation requests. + + + Defines an unhandled exception logger. + + + Logs an unhandled exception. + A task representing the asynchronous exception logging operation. + The exception logger context. + The token to monitor for cancellation requests. + + + Provides information about an action method, such as its name, controller, parameters, attributes, and filters. + + + Initializes a new instance of the class. + + + Returns the filters that are associated with this action method. + The filters that are associated with this action method. + The configuration. + The action descriptor. + + + Represents the base class for all action-filter attributes. + + + Initializes a new instance of the class. + + + Occurs after the action method is invoked. + The action executed context. + + + + Occurs before the action method is invoked. + The action context. + + + + Executes the filter action asynchronously. + The newly created task for this operation. + The action context. + The cancellation token assigned for this task. + The delegate function to continue after the action method is invoked. + + + Provides details for authorization filter. + + + Initializes a new instance of the class. + + + Calls when a process requests authorization. + The action context, which encapsulates information for using . + + + + Executes the authorization filter during synchronization. + The authorization filter during synchronization. + The action context, which encapsulates information for using . + The cancellation token that cancels the operation. + A continuation of the operation. + + + Represents the configuration filter provider. + + + Initializes a new instance of the class. + + + Returns the filters that are associated with this configuration method. + The filters that are associated with this configuration method. + The configuration. + The action descriptor. + + + Represents the attributes for the exception filter. + + + Initializes a new instance of the class. + + + Raises the exception event. + The context for the action. + + + + Asynchronously executes the exception filter. + The result of the execution. + The context for the action. + The cancellation context. + + + Represents the base class for action-filter attributes. + + + Initializes a new instance of the class. + + + Gets a value that indicates whether multiple filters are allowed. + true if multiple filters are allowed; otherwise, false. + + + Provides information about the available action filters. + + + Initializes a new instance of the class. + The instance of this class. + The scope of this class. + + + Gets or sets an instance of the . + A . + + + Gets or sets the scope . + The scope of the FilterInfo. + + + Defines values that specify the order in which filters run within the same filter type and filter order. + + + Specifies an order after Controller. + + + Specifies an order before Action and after Global. + + + Specifies an action before Controller. + + + Represents the action of the HTTP executed context. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The action context. + The exception. + + + Gets or sets the HTTP action context. + The HTTP action context. + + + Gets or sets the exception that was raised during the execution. + The exception that was raised during the execution. + + + Gets the object for the context. + The object for the context. + + + Gets or sets the for the context. + The for the context. + + + Represents an authentication challenge context containing information for executing an authentication challenge. + + + Initializes a new instance of the class. + The action context. + The current action result. + + + Gets the action context. + + + Gets the request message. + + + Gets or sets the action result to execute. + + + Represents an authentication context containing information for performing authentication. + + + Initializes a new instance of the class. + The action context. + The current principal. + + + Gets the action context. + The action context. + + + Gets or sets an action result that will produce an error response (if authentication failed; otherwise, null). + An action result that will produce an error response. + + + Gets or sets the authenticated principal. + The authenticated principal. + + + Gets the request message. + The request message. + + + Represents a collection of HTTP filters. + + + Initializes a new instance of the class. + + + Adds an item at the end of the collection. + The item to add to the collection. + + + + Removes all item in the collection. + + + Determines whether the collection contains the specified item. + true if the collection contains the specified item; otherwise, false. + The item to check. + + + Gets the number of elements in the collection. + The number of elements in the collection. + + + Gets an enumerator that iterates through the collection. + An enumerator object that can be used to iterate through the collection. + + + Removes the specified item from the collection. + The item to remove in the collection. + + + Gets an enumerator that iterates through the collection. + An enumerator object that can be used to iterate through the collection. + + + Defines the methods that are used in an action filter. + + + Executes the filter action asynchronously. + The newly created task for this operation. + The action context. + The cancellation token assigned for this task. + The delegate function to continue after the action method is invoked. + + + Defines a filter that performs authentication. + + + Authenticates the request. + A Task that will perform authentication. + The authentication context. + The token to monitor for cancellation requests. + + + + Defines the methods that are required for an authorization filter. + + + Executes the authorization filter to synchronize. + The authorization filter to synchronize. + The action context. + The cancellation token associated with the filter. + The continuation. + + + Defines the methods that are required for an exception filter. + + + Executes an asynchronous exception filter. + An asynchronous exception filter. + The action executed context. + The cancellation token. + + + Defines the methods that are used in a filter. + + + Gets or sets a value indicating whether more than one instance of the indicated attribute can be specified for a single program element. + true if more than one instance is allowed to be specified; otherwise, false. The default is false. + + + Provides filter information. + + + Returns an enumeration of filters. + An enumeration of filters. + The HTTP configuration. + The action descriptor. + + + + + Provides common keys for properties stored in the + + + Provides a key for the client certificate for this request. + + + Provides a key for the associated with this request. + + + Provides a key for the collection of resources that should be disposed when a request is disposed. + + + Provides a key for the associated with this request. + + + Provides a key for the associated with this request. + + + Provides a key for the associated with this request. + + + Provides a key that indicates whether error details are to be included in the response for this HTTP request. + + + Provides a key that indicates whether the request is a batch request. + + + Provides a key that indicates whether the request originates from a local address. + + + Provides a key that indicates whether the request failed to match a route. + + + Provides a key for the for this request. + + + Provides a key for the stored in . This is the correlation ID for that request. + + + Provides a key for the parsed query string stored in . + + + Provides a key for a delegate which can retrieve the client certificate for this request. + + + Provides a key for the current stored in Properties(). If Current() is null then no context is stored. + + + Interface for controlling the use of buffering requests and responses in the host. If a host provides support for buffering requests and/or responses then it can use this interface to determine the policy for when buffering is to be used. + + + Determines whether the host should buffer the entity body. + true if buffering should be used; otherwise a streamed request should be used. + The host context. + + + Determines whether the host should buffer the entity body. + true if buffering should be used; otherwise a streamed response should be used. + The HTTP response message. + + + Represents a message handler that suppresses host authentication results. + + + Initializes a new instance of the class. + + + Asynchronously sends a request message. + That task that completes the asynchronous operation. + The request message to send. + The cancellation token. + + + Represents the metadata class of the ModelMetadata. + + + Initializes a new instance of the class. + The provider. + The type of the container. + The model accessor. + The type of the model. + The name of the property. + + + Gets a dictionary that contains additional metadata about the model. + A dictionary that contains additional metadata about the model. + + + Gets or sets the type of the container for the model. + The type of the container for the model. + + + Gets or sets a value that indicates whether empty strings that are posted back in forms should be converted to null. + true if empty strings that are posted back in forms should be converted to null; otherwise, false. The default value is true. + + + Gets or sets the description of the model. + The description of the model. The default value is null. + + + Gets the display name for the model. + The display name for the model. + + + Gets a list of validators for the model. + A list of validators for the model. + The validator providers for the model. + + + Gets or sets a value that indicates whether the model is a complex type. + A value that indicates whether the model is considered a complex. + + + Gets a value that indicates whether the type is nullable. + true if the type is nullable; otherwise, false. + + + Gets or sets a value that indicates whether the model is read-only. + true if the model is read-only; otherwise, false. + + + Gets the value of the model. + The model value can be null. + + + Gets the type of the model. + The type of the model. + + + Gets a collection of model metadata objects that describe the properties of the model. + A collection of model metadata objects that describe the properties of the model. + + + Gets the property name. + The property name. + + + Gets or sets the provider. + The provider. + + + Provides an abstract base class for a custom metadata provider. + + + Initializes a new instance of the class. + + + Gets a ModelMetadata object for each property of a model. + A ModelMetadata object for each property of a model. + The container. + The type of the container. + + + Gets a metadata for the specified property. + The metadata model for the specified property. + The model accessor. + The type of the container. + The property to get the metadata model for. + + + Gets the metadata for the specified model accessor and model type. + The metadata. + The model accessor. + The type of the mode. + + + Provides an abstract class to implement a metadata provider. + The type of the model metadata. + + + Initializes a new instance of the class. + + + When overridden in a derived class, creates the model metadata for the property using the specified prototype. + The model metadata for the property. + The prototype from which to create the model metadata. + The model accessor. + + + When overridden in a derived class, creates the model metadata for the property. + The model metadata for the property. + The set of attributes. + The type of the container. + The type of the model. + The name of the property. + + + Retrieves a list of properties for the model. + A list of properties for the model. + The model container. + The type of the container. + + + Retrieves the metadata for the specified property using the container type and property name. + The metadata for the specified property. + The model accessor. + The type of the container. + The name of the property. + + + Returns the metadata for the specified property using the type of the model. + The metadata for the specified property. + The model accessor. + The type of the container. + + + Provides prototype cache data for . + + + Initializes a new instance of the class. + The attributes that provides data for the initialization. + + + Gets or sets the metadata display attribute. + The metadata display attribute. + + + Gets or sets the metadata display format attribute. + The metadata display format attribute. + + + + Gets or sets the metadata editable attribute. + The metadata editable attribute. + + + Gets or sets the metadata read-only attribute. + The metadata read-only attribute. + + + Provides a container for common metadata, for the class, for a data model. + + + Initializes a new instance of the class. + The prototype used to initialize the model metadata. + The model accessor. + + + Initializes a new instance of the class. + The metadata provider. + The type of the container. + The type of the model. + The name of the property. + The attributes that provides data for the initialization. + + + Retrieves a value that indicates whether empty strings that are posted back in forms should be converted to null. + true if empty strings that are posted back in forms should be converted to null; otherwise, false. + + + Retrieves the description of the model. + The description of the model. + + + Retrieves a value that indicates whether the model is read-only. + true if the model is read-only; otherwise, false. + + + + Provides prototype cache data for the . + The type of prototype cache. + + + Initializes a new instance of the class. + The prototype. + The model accessor. + + + Initializes a new instance of the class. + The provider. + The type of container. + The type of the model. + The name of the property. + The prototype cache. + + + Indicates whether empty strings that are posted back in forms should be computed and converted to null. + true if empty strings that are posted back in forms should be computed and converted to null; otherwise, false. + + + Indicates the computation value. + The computation value. + + + Gets a value that indicates whether the model is a complex type. + A value that indicates whether the model is considered a complex type by the Web API framework. + + + Gets a value that indicates whether the model to be computed is read-only. + true if the model to be computed is read-only; otherwise, false. + + + Gets or sets a value that indicates whether empty strings that are posted back in forms should be converted to null. + true if empty strings that are posted back in forms should be converted to null; otherwise, false. The default value is true. + + + Gets or sets the description of the model. + The description of the model. + + + Gets a value that indicates whether the model is a complex type. + A value that indicates whether the model is considered a complex type by the Web API framework. + + + Gets or sets a value that indicates whether the model is read-only. + true if the model is read-only; otherwise, false. + + + Gets or sets a value that indicates whether the prototype cache is updating. + true if the prototype cache is updating; otherwise, false. + + + Implements the default model metadata provider. + + + Initializes a new instance of the class. + + + Creates the metadata from prototype for the specified property. + The metadata for the property. + The prototype. + The model accessor. + + + Creates the metadata for the specified property. + The metadata for the property. + The attributes. + The type of the container. + The type of the model. + The name of the property. + + + Represents an empty model metadata provider. + + + Initializes a new instance of the class. + + + Creates metadata from prototype. + The metadata. + The model metadata prototype. + The model accessor. + + + Creates a prototype of the metadata provider of the . + A prototype of the metadata provider. + The attributes. + The type of container. + The type of model. + The name of the property. + + + Represents the binding directly to the cancellation token. + + + Initializes a new instance of the class. + The binding descriptor. + + + Executes the binding during synchronization. + The binding during synchronization. + The metadata provider. + The action context. + The notification after the cancellation of the operations. + + + Represents an attribute that invokes a custom model binder. + + + Initializes a new instance of the class. + + + Retrieves the associated model binder. + A reference to an object that implements the interface. + + + Represents the default action value of the binder. + + + Initializes a new instance of the class. + + + Default implementation of the interface. This interface is the primary entry point for binding action parameters. + The associated with the . + The action descriptor. + + + Gets the associated with the . + The associated with the . + The parameter descriptor. + + + Defines a binding error. + + + Initializes a new instance of the class. + The error descriptor. + The message. + + + Gets the error message. + The error message. + + + Executes the binding method during synchronization. + The metadata provider. + The action context. + The cancellation Token value. + + + Represents parameter binding that will read from the body and invoke the formatters. + + + Initializes a new instance of the class. + The descriptor. + The formatter. + The body model validator. + + + Gets or sets an interface for the body model validator. + An interface for the body model validator. + + + Gets the error message. + The error message. + + + Asynchronously execute the binding of . + The result of the action. + The metadata provider. + The context associated with the action. + The cancellation token. + + + Gets or sets an enumerable object that represents the formatter for the parameter binding. + An enumerable object that represents the formatter for the parameter binding. + + + Asynchronously reads the content of . + The result of the action. + The request. + The type. + The formatter. + The format logger. + + + + Gets whether the will read body. + True if the will read body; otherwise, false. + + + Represents the extensions for the collection of form data. + + + Reads the collection extensions with specified type. + The read collection extensions. + The form data. + The generic type. + + + Reads the collection extensions with specified type. + The collection extensions. + The form data. + The name of the model. + The required member selector. + The formatter logger. + The generic type. + + + + + + Reads the collection extensions with specified type. + The collection extensions with specified type. + The form data. + The type of the object. + + + Reads the collection extensions with specified type and model name. + The collection extensions. + The form data. + The type of the object. + The name of the model. + The required member selector. + The formatter logger. + + + Deserialize the form data to the given type, using model binding. + best attempt to bind the object. The best attempt may be null. + collection with parsed form url data + target type to read as + null or empty to read the entire form as a single object. This is common for body data. Or the name of a model to do a partial binding against the form data. This is common for extracting individual fields. + The used to determine required members. + The to log events to. + The configuration to pick binder from. Can be null if the config was not created already. In that case a new config is created. + + + + + + + + Enumerates the behavior of the HTTP binding. + + + Never use HTTP binding. + + + The optional binding behavior + + + HTTP binding is required. + + + Provides a base class for model-binding behavior attributes. + + + Initializes a new instance of the class. + The behavior. + + + Gets or sets the behavior category. + The behavior category. + + + Gets the unique identifier for this attribute. + The id for this attribute. + + + Parameter binds to the request. + + + Initializes a new instance of the class. + The parameter descriptor. + + + Asynchronously executes parameter binding. + The binded parameter. + The metadata provider. + The action context. + The cancellation token. + + + Defines the methods that are required for a model binder. + + + Binds the model to a value by using the specified controller context and binding context. + true if model binding is successful; otherwise, false. + The action context. + The binding context. + + + Represents a value provider for parameter binding. + + + Gets the instances used by this parameter binding. + The instances used by this parameter binding. + + + Represents the class for handling HTML form URL-ended data, also known as application/x-www-form-urlencoded. + + + Initializes a new instance of the class. + + + + Determines whether this can read objects of the specified . + true if objects of this type can be read; otherwise false. + The type of object that will be read. + + + Reads an object of the specified from the specified stream. This method is called during deserialization. + A whose result will be the object instance that has been read. + The type of object to read. + The from which to read. + The content being read. + The to log events to. + + + Specify this parameter uses a model binder. This can optionally specify the specific model binder and value providers that drive that model binder. Derived attributes may provide convenience settings for the model binder or value provider. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The type of model binder. + + + Gets or sets the type of model binder. + The type of model binder. + + + Gets the binding for a parameter. + The that contains the binding. + The parameter to bind. + + + Get the IModelBinder for this type. + a non-null model binder. + The configuration. + model type that the binder is expected to bind. + + + Gets the model binder provider. + The instance. + The configuration object. + + + Gets the value providers that will be fed to the model binder. + A collection of instances. + The configuration object. + + + Gets or sets the name to consider as the parameter name during model binding. + The parameter name to consider. + + + Gets or sets a value that specifies whether the prefix check should be suppressed. + true if the prefix check should be suppressed; otherwise, false. + + + Provides a container for model-binder configuration. + + + Gets or sets the name of the resource file (class key) that contains localized string values. + The name of the resource file (class key). + + + Gets or sets the current provider for type-conversion error message. + The current provider for type-conversion error message. + + + Gets or sets the current provider for value-required error messages. + The error message provider. + + + Provides a container for model-binder error message provider. + + + Describes a parameter that gets bound via ModelBinding. + + + Initializes a new instance of the class. + The parameter descriptor. + The model binder. + The collection of value provider factory. + + + Gets the model binder. + The model binder. + + + Asynchronously executes the parameter binding via the model binder. + The task that is signaled when the binding is complete. + The metadata provider to use for validation. + The action context for the binding. + The cancellation token assigned for this task for cancelling the binding operation. + + + Gets the collection of value provider factory. + The collection of value provider factory. + + + Provides an abstract base class for model binder providers. + + + Initializes a new instance of the class. + + + Finds a binder for the given type. + A binder, which can attempt to bind this type. Or null if the binder knows statically that it will never be able to bind the type. + A configuration object. + The type of the model to bind against. + + + Provides the context in which a model binder functions. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The binding context. + + + Gets or sets a value that indicates whether the binder should use an empty prefix. + true if the binder should use an empty prefix; otherwise, false. + + + Gets or sets the model. + The model. + + + Gets or sets the model metadata. + The model metadata. + + + Gets or sets the name of the model. + The name of the model. + + + Gets or sets the state of the model. + The state of the model. + + + Gets or sets the type of the model. + The type of the model. + + + Gets the property metadata. + The property metadata. + + + Gets or sets the validation node. + The validation node. + + + Gets or sets the value provider. + The value provider. + + + Represents an error that occurs during model binding. + + + Initializes a new instance of the class by using the specified exception. + The exception. + + + Initializes a new instance of the class by using the specified exception and error message. + The exception. + The error message + + + Initializes a new instance of the class by using the specified error message. + The error message + + + Gets or sets the error message. + The error message. + + + Gets or sets the exception object. + The exception object. + + + Represents a collection of instances. + + + Initializes a new instance of the class. + + + Adds the specified Exception object to the model-error collection. + The exception. + + + Adds the specified error message to the model-error collection. + The error message. + + + Encapsulates the state of model binding to a property of an action-method argument, or to the argument itself. + + + Initializes a new instance of the class. + + + Gets a object that contains any errors that occurred during model binding. + The model state errors. + + + Gets a object that encapsulates the value that was being bound during model binding. + The model state value. + + + Represents the state of an attempt to bind a posted form to an action method, which includes validation information. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class by using values that are copied from the specified model-state dictionary. + The dictionary. + + + Adds the specified item to the model-state dictionary. + The object to add to the model-state dictionary. + + + Adds an element that has the specified key and value to the model-state dictionary. + The key of the element to add. + The value of the element to add. + + + Adds the specified model error to the errors collection for the model-state dictionary that is associated with the specified key. + The key. + The exception. + + + Adds the specified error message to the errors collection for the model-state dictionary that is associated with the specified key. + The key. + The error message. + + + Removes all items from the model-state dictionary. + + + Determines whether the model-state dictionary contains a specific value. + true if item is found in the model-state dictionary; otherwise, false. + The object to locate in the model-state dictionary. + + + Determines whether the model-state dictionary contains the specified key. + true if the model-state dictionary contains the specified key; otherwise, false. + The key to locate in the model-state dictionary. + + + Copies the elements of the model-state dictionary to an array, starting at a specified index. + The array. The array must have zero-based indexing. + The zero-based index in array at which copying starts. + + + Gets the number of key/value pairs in the collection. + The number of key/value pairs in the collection. + + + Returns an enumerator that can be used to iterate through the collection. + An enumerator that can be used to iterate through the collection. + + + Gets a value that indicates whether the collection is read-only. + true if the collection is read-only; otherwise, false. + + + Gets a value that indicates whether this instance of the model-state dictionary is valid. + true if this instance is valid; otherwise, false. + + + Determines whether there are any objects that are associated with or prefixed with the specified key. + true if the model-state dictionary contains a value that is associated with the specified key; otherwise, false. + The key. + + + Gets or sets the value that is associated with the specified key. + The model state item. + The key. + + + Gets a collection that contains the keys in the dictionary. + A collection that contains the keys of the model-state dictionary. + + + Copies the values from the specified object into this dictionary, overwriting existing values if keys are the same. + The dictionary. + + + Removes the first occurrence of the specified object from the model-state dictionary. + true if item was successfully removed the model-state dictionary; otherwise, false. This method also returns false if item is not found in the model-state dictionary. + The object to remove from the model-state dictionary. + + + Removes the element that has the specified key from the model-state dictionary. + true if the element is successfully removed; otherwise, false. This method also returns false if key was not found in the model-state dictionary. + The key of the element to remove. + + + Sets the value for the specified key by using the specified value provider dictionary. + The key. + The value. + + + Returns an enumerator that iterates through a collection. + An IEnumerator object that can be used to iterate through the collection. + + + Attempts to gets the value that is associated with the specified key. + true if the object contains an element that has the specified key; otherwise, false. + The key of the value to get. + The value associated with the specified key. + + + Gets a collection that contains the values in the dictionary. + A collection that contains the values of the model-state dictionary. + + + Collection of functions that can produce a parameter binding for a given parameter. + + + Initializes a new instance of the class. + + + Adds function to the end of the collection. The function added is a wrapper around funcInner that checks that parameterType matches typeMatch. + type to match against HttpParameterDescriptor.ParameterType + inner function that is invoked if type match succeeds + + + Insert a function at the specified index in the collection. /// The function added is a wrapper around funcInner that checks that parameterType matches typeMatch. + index to insert at. + type to match against HttpParameterDescriptor.ParameterType + inner function that is invoked if type match succeeds + + + Execute each binding function in order until one of them returns a non-null binding. + the first non-null binding produced for the parameter. Of null if no binding is produced. + parameter to bind. + + + Maps a browser request to an array. + The type of the array. + + + Initializes a new instance of the class. + + + Indicates whether the model is binded. + true if the specified model is binded; otherwise, false. + The action context. + The binding context. + + + Converts the collection to an array. + true in all cases. + The action context. + The binding context. + The new collection. + + + Provides a model binder for arrays. + + + Initializes a new instance of the class. + + + Returns a model binder for arrays. + A model binder object or null if the attempt to get a model binder is unsuccessful. + The configuration. + The type of model. + + + Maps a browser request to a collection. + The type of the collection. + + + Initializes a new instance of the class. + + + Binds the model by using the specified execution context and binding context. + true if model binding is successful; otherwise, false. + The action context. + The binding context. + + + Provides a way for derived classes to manipulate the collection before returning it from the binder. + true in all cases. + The action context. + The binding context. + The new collection. + + + Provides a model binder for a collection. + + + Initializes a new instance of the class. + + + Retrieves a model binder for a collection. + The model binder. + The configuration of the model. + The type of the model. + + + Represents a data transfer object (DTO) for a complex model. + + + Initializes a new instance of the class. + The model metadata. + The collection of property metadata. + + + Gets or sets the model metadata of the . + The model metadata of the . + + + Gets or sets the collection of property metadata of the . + The collection of property metadata of the . + + + Gets or sets the results of the . + The results of the . + + + Represents a model binder for object. + + + Initializes a new instance of the class. + + + Determines whether the specified model is binded. + true if the specified model is binded; otherwise, false. + The action context. + The binding context. + + + Represents a complex model that invokes a model binder provider. + + + Initializes a new instance of the class. + + + Retrieves the associated model binder. + The model binder. + The configuration. + The type of the model to retrieve. + + + Represents the result for object. + + + Initializes a new instance of the class. + The object model. + The validation node. + + + Gets or sets the model for this object. + The model for this object. + + + Gets or sets the for this object. + The for this object. + + + Represents an that delegates to one of a collection of instances. + + + Initializes a new instance of the class. + An enumeration of binders. + + + Initializes a new instance of the class. + An array of binders. + + + Indicates whether the specified model is binded. + true if the model is binded; otherwise, false. + The action context. + The binding context. + + + Represents the class for composite model binder providers. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + A collection of + + + Gets the binder for the model. + The binder for the model. + The binder configuration. + The type of the model. + + + Gets the providers for the composite model binder. + The collection of providers. + + + Maps a browser request to a dictionary data object. + The type of the key. + The type of the value. + + + Initializes a new instance of the class. + + + Converts the collection to a dictionary. + true in all cases. + The action context. + The binding context. + The new collection. + + + Provides a model binder for a dictionary. + + + Initializes a new instance of the class. + + + Retrieves the associated model binder. + The associated model binder. + The configuration to use. + The type of model. + + + Maps a browser request to a key/value pair data object. + The type of the key. + The type of the value. + + + Initializes a new instance of the class. + + + Binds the model by using the specified execution context and binding context. + true if model binding is successful; otherwise, false. + The action context. + The binding context. + + + Provides a model binder for a collection of key/value pairs. + + + Initializes a new instance of the class. + + + Retrieves the associated model binder. + The associated model binder. + The configuration. + The type of model. + + + Maps a browser request to a mutable data object. + + + Initializes a new instance of the class. + + + Binds the model by using the specified action context and binding context. + true if binding is successful; otherwise, false. + The action context. + The binding context. + + + Retrieves a value that indicates whether a property can be updated. + true if the property can be updated; otherwise, false. + The metadata for the property to be evaluated. + + + Creates an instance of the model. + The newly created model object. + The action context. + The binding context. + + + Creates a model instance if an instance does not yet exist in the binding context. + The action context. + The binding context. + + + Retrieves metadata for properties of the model. + The metadata for properties of the model. + The action context. + The binding context. + + + Sets the value of a specified property. + The action context. + The binding context. + The metadata for the property to set. + The validation information about the property. + The validator for the model. + + + Provides a model binder for mutable objects. + + + Initializes a new instance of the class. + + + Retrieves the model binder for the specified type. + The model binder. + The configuration. + The type of the model to retrieve. + + + Provides a simple model binder for this model binding class. + + + Initializes a new instance of the class. + The model type. + The model binder factory. + + + Initializes a new instance of the class by using the specified model type and the model binder. + The model type. + The model binder. + + + Returns a model binder by using the specified execution context and binding context. + The model binder, or null if the attempt to get a model binder is unsuccessful. + The configuration. + The model type. + + + Gets the type of the model. + The type of the model. + + + Gets or sets a value that specifies whether the prefix check should be suppressed. + true if the prefix check should be suppressed; otherwise, false. + + + Maps a browser request to a data object. This type is used when model binding requires conversions using a .NET Framework type converter. + + + Initializes a new instance of the class. + + + Binds the model by using the specified controller context and binding context. + true if model binding is successful; otherwise, false. + The action context. + The binding context. + + + Provides a model binder for a model that requires type conversion. + + + Initializes a new instance of the class. + + + Retrieve a model binder for a model that requires type conversion. + The model binder, or Nothing if the type cannot be converted or there is no value to convert. + The configuration of the binder. + The type of the model. + + + Maps a browser request to a data object. This class is used when model binding does not require type conversion. + + + Initializes a new instance of the class. + + + Binds the model by using the specified execution context and binding context. + true if model binding is successful; otherwise, false. + The action context. + The binding context. + + + Provides a model binder for a model that does not require type conversion. + + + Initializes a new instance of the class. + + + Retrieves the associated model binder. + The associated model binder. + The configuration. + The type of model. + + + Represents an action result that returns response and performs content negotiation on an see with . + + + Initializes a new instance of the class. + The user-visible error message. + The content negotiator to handle content negotiation. + The request message which led to this result. + The formatters to use to negotiate and format the content. + + + Initializes a new instance of the class. + The user-visible error message. + The controller from which to obtain the dependencies needed for execution. + + + Gets the content negotiator to handle content negotiation. + Returns . + + + Returns . + + + Gets the formatters to use to negotiate and format the content. + Returns . + + + Gets the user-visible error message. + Returns . + + + Gets the request message which led to this result. + Returns . + + + Represents an action result that returns an empty response. + + + Initializes a new instance of the class. + The request message which led to this result. + + + Initializes a new instance of the class. + The controller from which to obtain the dependencies needed for execution. + + + Asynchronously executes the request. + The task that completes the execute operation. + The cancellation token. + + + Gets the request message which led to this result. + The request message which led to this result. + + + Represents an action result that returns an empty HttpStatusCode.Conflict response. + + + Initializes a new instance of the class. + The request message which led to this result. + + + Initializes a new instance of the class. + The controller from which to obtain the dependencies needed for execution. + + + Executes asynchronously the operation of the conflict result. + Asynchronously executes the specified task. + The cancellation token. + + + Gets the request message which led to this result. + The HTTP request message which led to this result. + + + Represents an action result that performs route generation and content negotiation and returns a response when content negotiation succeeds. + The type of content in the entity body. + + + Initializes a new instance of the class with the values provided. + The name of the route to use for generating the URL. + The route data to use for generating the URL. + The content value to negotiate and format in the entity body. + The controller from which to obtain the dependencies needed for execution. + + + Initializes a new instance of the class with the values provided. + The name of the route to use for generating the URL. + The route data to use for generating the URL. + The content value to negotiate and format in the entity body. + The factory to use to generate the route URL. + The content negotiator to handle content negotiation. + The request message which led to this result. + The formatters to use to negotiate and format the content. + + + Gets the content value to negotiate and format in the entity body. + + + Gets the content negotiator to handle content negotiation. + + + + Gets the formatters to use to negotiate and format the content. + + + Gets the request message which led to this result. + + + Gets the name of the route to use for generating the URL. + + + Gets the route data to use for generating the URL. + + + Gets the factory to use to generate the route URL. + + + Represents an action result that performs content negotiation and returns a response when it succeeds. + The type of content in the entity body. + + + Initializes a new instance of the class with the values provided. + The content value to negotiate and format in the entity body. + The location at which the content has been created. + The content negotiator to handle content negotiation. + The request message which led to this result. + The formatters to use to negotiate and format the content. + + + Initializes a new instance of the class with the values provided. + The location at which the content has been created. + The content value to negotiate and format in the entity body. + The controller from which to obtain the dependencies needed for execution. + + + Gets the content value to negotiate and format in the entity body. + The content value to negotiate and format in the entity body. + + + Gets the content negotiator to handle content negotiation. + The content negotiator to handle content negotiation. + + + Executes asynchronously the operation of the created negotiated content result. + Asynchronously executes a return value. + The cancellation token. + + + Gets the formatters to use to negotiate and format the content. + The formatters to use to negotiate and format the content. + + + Gets the location at which the content has been created. + The location at which the content has been created. + + + Gets the request message which led to this result. + The HTTP request message which led to this result. + + + Represents an action result that returns a response and performs content negotiation on an  based on an . + + + Initializes a new instance of the class. + The exception to include in the error. + true if the error should include exception messages; otherwise, false . + The content negotiator to handle content negotiation. + The request message which led to this result. + The formatters to use to negotiate and format the content. + + + Initializes a new instance of the class. + The exception to include in the error. + The controller from which to obtain the dependencies needed for execution. + + + Gets the content negotiator to handle content negotiation. + Returns . + + + Gets the exception to include in the error. + Returns . + + + Returns . + + + Gets the formatters to use to negotiate and format the content. + Returns . + + + Gets a value indicating whether the error should include exception messages. + Returns . + + + Gets the request message which led to this result. + Returns . + + + Represents an action result that returns formatted content. + The type of content in the entity body. + + + Initializes a new instance of the class with the values provided. + The HTTP status code for the response message. + The content value to format in the entity body. + The formatter to use to format the content. + The value for the Content-Type header, or to have the formatter pick a default value. + The request message which led to this result. + + + Initializes a new instance of the class with the values provided. + The HTTP status code for the response message. + The content value to format in the entity body. + The formatter to use to format the content. + The value for the Content-Type header, or to have the formatter pick a default value. + The controller from which to obtain the dependencies needed for execution. + + + Gets the content value to format in the entity body. + + + + Gets the formatter to use to format the content. + + + Gets the value for the Content-Type header, or to have the formatter pick a default value. + + + Gets the request message which led to this result. + + + Gets the HTTP status code for the response message. + + + Represents an action result that returns an empty response. + + + Initializes a new instance of the class. + The request message which led to this result. + + + Initializes a new instance of the class. + The controller from which to obtain the dependencies needed for execution. + + + Returns . + + + Gets the request message which led to this result. + Returns . + + + Represents an action result that returns a response and performs content negotiation on an based on a . + + + Initializes a new instance of the class. + The model state to include in the error. + true if the error should include exception messages; otherwise, false. + The content negotiator to handle content negotiation. + The request message which led to this result. + The formatters to use to negotiate and format the content. + + + Initializes a new instance of the class. + The model state to include in the error. + The controller from which to obtain the dependencies needed for execution. + + + Gets the content negotiator to handle content negotiation. + The content negotiator to handle content negotiation. + + + Creates a response message asynchronously. + A task that, when completed, contains the response message. + The token to monitor for cancellation requests. + + + Gets the formatters to use to negotiate and format the content. + The formatters to use to negotiate and format the content. + + + Gets a value indicating whether the error should include exception messages. + true if the error should include exception messages; otherwise, false. + + + Gets the model state to include in the error. + The model state to include in the error. + + + Gets the request message which led to this result. + The request message which led to this result. + + + Represents an action result that returns an response with JSON data. + The type of content in the entity body. + + + Initializes a new instance of the class with the values provided. + The content value to serialize in the entity body. + The serializer settings. + The content encoding. + The request message which led to this result. + + + Initializes a new instance of the class with the values provided. + The content value to serialize in the entity body. + The serializer settings. + The content encoding. + The controller from which to obtain the dependencies needed for execution. + + + Gets the content value to serialize in the entity body. + The content value to serialize in the entity body. + + + Gets the content encoding. + The content encoding. + + + Creates a response message asynchronously. + A task that, when completed, contains the response message. + The token to monitor for cancellation requests. + + + Gets the request message which led to this result. + The request message which led to this result. + + + Gets the serializer settings. + The serializer settings. + + + Represents an action result that performs content negotiation. + The type of content in the entity body. + + + Initializes a new instance of the class with the values provided. + The HTTP status code for the response message. + The content value to negotiate and format in the entity body. + The content negotiator to handle content negotiation. + The request message which led to this result. + The formatters to use to negotiate and format the content. + + + Initializes a new instance of the class with the values provided. + The HTTP status code for the response message. + The content value to negotiate and format in the entity body. + The controller from which to obtain the dependencies needed for execution. + + + Gets the content value to negotiate and format in the entity body. + The content value to negotiate and format in the entity body. + + + Gets the content negotiator to handle content negotiation. + The content negotiator to handle content negotiation. + + + Executes asynchronously an HTTP negotiated content results. + Asynchronously executes an HTTP negotiated content results. + The cancellation token. + + + Gets the formatters to use to negotiate and format the content. + The formatters to use to negotiate and format the content. + + + Gets the request message which led to this result. + The HTTP request message which led to this result. + + + Gets the HTTP status code for the response message. + The HTTP status code for the response message. + + + Represents an action result that returns an empty response. + + + Initializes a new instance of the class. + The request message which led to this result. + + + Initializes a new instance of the class. + The controller from which to obtain the dependencies needed for execution. + + + + Gets the request message which led to this result. + + + Represents an action result that performs content negotiation and returns an HttpStatusCode.OK response when it succeeds. + The type of content in the entity body. + + + Initializes a new instance of the class with the values provided. + The content value to negotiate and format in the entity body. + The content negotiator to handle content negotiation. + The request message which led to this result. + The formatters to use to negotiate and format the content. + + + Initializes a new instance of the class with the values provided. + The content value to negotiate and format in the entity body. + The controller from which to obtain the dependencies needed for execution. + + + Gets the content value to negotiate and format in the entity body. + + + Gets the content negotiator to handle content negotiation. + + + + Gets the formatters to use to negotiate and format the content. + + + Gets the request message which led to this result. + + + Represents an action result that returns an empty HttpStatusCode.OK response. + + + Initializes a new instance of the class. + The request message which led to this result. + + + Initializes a new instance of the class. + The controller from which to obtain the dependencies needed for execution. + + + Executes asynchronously. + Returns the task. + The cancellation token. + + + Gets a HTTP request message for the results. + A HTTP request message for the results. + + + Represents an action result for a <see cref="F:System.Net.HttpStatusCode.Redirect"/>. + + + Initializes a new instance of the <see cref="T:System.Web.Http.Results.RedirectResult"/> class with the values provided. + The location to which to redirect. + The request message which led to this result. + + + Initializes a new instance of the <see cref="T:System.Web.Http.Results.RedirectResult"/> class with the values provided. + The location to which to redirect. + The controller from which to obtain the dependencies needed for execution. + + + Returns . + + + Gets the location at which the content has been created. + Returns . + + + Gets the request message which led to this result. + Returns . + + + Represents an action result that performs route generation and returns a <see cref="F:System.Net.HttpStatusCode.Redirect"/> response. + + + Initializes a new instance of the <see cref="T:System.Web.Http.Results.RedirectToRouteResult"/> class with the values provided. + The name of the route to use for generating the URL. + The route data to use for generating the URL. + The controller from which to obtain the dependencies needed for execution. + + + Initializes a new instance of the <see cref="T:System.Web.Http.Results.RedirectToRouteResult"/> class with the values provided. + The name of the route to use for generating the URL. + The route data to use for generating the URL. + The factory to use to generate the route URL. + The request message which led to this result. + + + Returns . + + + Gets the request message which led to this result. + Returns . + + + Gets the name of the route to use for generating the URL. + Returns . + + + Gets the route data to use for generating the URL. + Returns . + + + Gets the factory to use to generate the route URL. + Returns . + + + Represents an action result that returns a specified response message. + + + Initializes a new instance of the class. + The response message. + + + + Gets the response message. + + + Represents an action result that returns a specified HTTP status code. + + + Initializes a new instance of the class. + The HTTP status code for the response message. + The request message which led to this result. + + + Initializes a new instance of the class. + The HTTP status code for the response message. + The controller from which to obtain the dependencies needed for execution. + + + Creates a response message asynchronously. + A task that, when completed, contains the response message. + The token to monitor for cancellation requests. + + + Gets the request message which led to this result. + The request message which led to this result. + + + Gets the HTTP status code for the response message. + The HTTP status code for the response message. + + + Represents an action result that returns an response. + + + Initializes a new instance of the class. + The WWW-Authenticate challenges. + The request message which led to this result. + + + Initializes a new instance of the class. + The WWW-Authenticate challenges. + The controller from which to obtain the dependencies needed for execution. + + + Gets the WWW-Authenticate challenges. + Returns . + + + Returns . + + + Gets the request message which led to this result. + Returns . + + + A default implementation of . + + + + Creates instances based on the provided factories and action. The route entries provide direct routing to the provided action. + A set of route entries. + The action descriptor. + The direct route factories. + The constraint resolver. + + + Gets a set of route factories for the given action descriptor. + A set of route factories. + The action descriptor. + + + Creates instances based on the provided factories, controller and actions. The route entries provided direct routing to the provided controller and can reach the set of provided actions. + A set of route entries. + The controller descriptor. + The action descriptors. + The direct route factories. + The constraint resolver. + + + Gets route factories for the given controller descriptor. + A set of route factories. + The controller descriptor. + + + Gets direct routes for the given controller descriptor and action descriptors based on attributes. + A set of route entries. + The controller descriptor. + The action descriptors for all actions. + The constraint resolver. + + + Gets the route prefix from the provided controller. + The route prefix or null. + The controller descriptor. + + + The default implementation of . Resolves constraints by parsing a constraint key and constraint arguments, using a map to resolve the constraint type, and calling an appropriate constructor for the constraint type. + + + Initializes a new instance of the class. + + + Gets the mutable dictionary that maps constraint keys to a particular constraint type. + The mutable dictionary that maps constraint keys to a particular constraint type. + + + Resolves the inline constraint. + The the inline constraint was resolved to. + The inline constraint to resolve. + + + Represents a context that supports creating a direct route. + + + Initializes a new instance of the class. + The route prefix, if any, defined by the controller. + The action descriptors to which to create a route. + The inline constraint resolver. + A value indicating whether the route is configured at the action or controller level. + + + Gets the action descriptors to which to create a route. + The action descriptors to which to create a route. + + + Creates a route builder that can build a route matching this context. + A route builder that can build a route matching this context. + The route template. + + + Creates a route builder that can build a route matching this context. + A route builder that can build a route matching this context. + The route template. + The inline constraint resolver to use, if any; otherwise, null. + + + Gets the inline constraint resolver. + The inline constraint resolver. + + + Gets the route prefix, if any, defined by the controller. + The route prefix, if any, defined by the controller. + + + Gets a value indicating whether the route is configured at the action or controller level. + true when the route is configured at the action level; otherwise false (if the route is configured at the controller level). + + + Enables you to define which HTTP verbs are allowed when ASP.NET routing determines whether a URL matches a route. + + + Initializes a new instance of the class by using the HTTP verbs that are allowed for the route. + The HTTP verbs that are valid for the route. + + + Gets or sets the collection of allowed HTTP verbs for the route. + A collection of allowed HTTP verbs for the route. + + + Determines whether the request was made with an HTTP verb that is one of the allowed verbs for the route. + When ASP.NET routing is processing a request, true if the request was made by using an allowed HTTP verb; otherwise, false. When ASP.NET routing is constructing a URL, true if the supplied values contain an HTTP verb that matches one of the allowed HTTP verbs; otherwise, false. The default is true. + The request that is being checked to determine whether it matches the URL. + The object that is being checked to determine whether it matches the URL. + The name of the parameter that is being checked. + An object that contains the parameters for a route. + An object that indicates whether the constraint check is being performed when an incoming request is processed or when a URL is generated. + + + Determines whether the request was made with an HTTP verb that is one of the allowed verbs for the route. + When ASP.NET routing is processing a request, true if the request was made by using an allowed HTTP verb; otherwise, false. When ASP.NET routing is constructing a URL, true if the supplied values contain an HTTP verb that matches one of the allowed HTTP verbs; otherwise, false. The default is true. + The request that is being checked to determine whether it matches the URL. + The object that is being checked to determine whether it matches the URL. + The name of the parameter that is being checked. + An object that contains the parameters for a route. + An object that indicates whether the constraint check is being performed when an incoming request is processed or when a URL is generated. + + + Represents a route class for self-host (i.e. hosted outside of ASP.NET). + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The route template. + + + Initializes a new instance of the class. + The route template. + The default values for the route parameters. + + + Initializes a new instance of the class. + The route template. + The default values for the route parameters. + The constraints for the route parameters. + + + Initializes a new instance of the class. + The route template. + The default values for the route parameters. + The constraints for the route parameters. + Any additional tokens for the route parameters. + + + Initializes a new instance of the class. + The route template. + The default values for the route parameters. + The constraints for the route parameters. + Any additional tokens for the route parameters. + The message handler that will be the recipient of the request. + + + Gets the constraints for the route parameters. + The constraints for the route parameters. + + + Gets any additional data tokens not used directly to determine whether a route matches an incoming . + Any additional data tokens not used directly to determine whether a route matches an incoming . + + + Gets the default values for route parameters if not provided by the incoming . + The default values for route parameters if not provided by the incoming . + + + Determines whether this route is a match for the incoming request by looking up the for the route. + The for a route if matches; otherwise null. + The virtual path root. + The HTTP request. + + + Attempts to generate a URI that represents the values passed in based on current values from the and new values using the specified . + A instance or null if URI cannot be generated. + The HTTP request message. + The route values. + + + Gets or sets the http route handler. + The http route handler. + + + Specifies the HTTP route key. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The HTTP request. + The constraints for the route parameters. + The name of the parameter. + The list of parameter values. + One of the enumeration values of the enumeration. + + + Gets the route template describing the URI pattern to match against. + The route template describing the URI pattern to match against. + + + Encapsulates information regarding the HTTP route. + + + Initializes a new instance of the class. + An object that defines the route. + + + Initializes a new instance of the class. + An object that defines the route. + The value. + + + Gets the object that represents the route. + the object that represents the route. + + + Gets a collection of URL parameter values and default values for the route. + An object that contains values that are parsed from the URL and from default values. + + + Removes all optional parameters that do not have a value from the route data. + + + If a route is really a union of other routes, return the set of sub routes. + Returns the set of sub routes contained within this route. + A union route data. + + + Removes all optional parameters that do not have a value from the route data. + The route data, to be mutated in-place. + + + Specifies an enumeration of route direction. + + + The UriGeneration direction. + + + The UriResolution direction. + + + Represents a route class for self-host of specified key/value pairs. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The dictionary. + + + Initializes a new instance of the class. + The key value. + + + Presents the data regarding the HTTP virtual path. + + + Initializes a new instance of the class. + The route of the virtual path. + The URL that was created from the route definition. + + + Gets or sets the route of the virtual path.. + The route of the virtual path. + + + Gets or sets the URL that was created from the route definition. + The URL that was created from the route definition. + + + Defines a builder that creates direct routes to actions (attribute routes). + + + Gets the action descriptors to which to create a route. + The action descriptors to which to create a route. + + + Creates a route entry based on the current property values. + The route entry created. + + + Gets or sets the route constraints. + The route constraints. + + + Gets or sets the route data tokens. + The route data tokens. + + + Gets or sets the route defaults. + The route defaults. + + + Gets or sets the route name, if any; otherwise null. + The route name, if any; otherwise null. + + + Gets or sets the route order. + The route order. + + + Gets or sets the route precedence. + The route precedence. + + + Gets a value indicating whether the route is configured at the action or controller level. + true when the route is configured at the action level; otherwise false (if the route is configured at the controller level). + + + Gets or sets the route template. + The route template. + + + Defines a factory that creates a route directly to a set of action descriptors (an attribute route). + + + Creates a direct route entry. + The direct route entry. + The context to use to create the route. + + + Defines a provider for routes that directly target action descriptors (attribute routes). + + + Gets the direct routes for a controller. + A set of route entries for the controller. + The controller descriptor. + The action descriptors. + The inline constraint resolver. + + + + defines the interface for a route expressing how to map an incoming to a particular controller and action. + + + Gets the constraints for the route parameters. + The constraints for the route parameters. + + + Gets any additional data tokens not used directly to determine whether a route matches an incoming . + The additional data tokens. + + + Gets the default values for route parameters if not provided by the incoming . + The default values for route parameters. + + + Determine whether this route is a match for the incoming request by looking up the <see cref="!:IRouteData" /> for the route. + The <see cref="!:RouteData" /> for a route if matches; otherwise null. + The virtual path root. + The request. + + + Gets a virtual path data based on the route and the values provided. + The virtual path data. + The request message. + The values. + + + Gets the message handler that will be the recipient of the request. + The message handler. + + + Gets the route template describing the URI pattern to match against. + The route template. + + + Represents a base class route constraint. + + + Determines whether this instance equals a specified route. + True if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Provides information about a route. + + + Gets the object that represents the route. + The object that represents the route. + + + Gets a collection of URL parameter values and default values for the route. + The values that are parsed from the URL and from default values. + + + Provides information for defining a route. + + + Gets the name of the route to generate. + + + Gets the order of the route relative to other routes. + + + Gets the route template describing the URI pattern to match against. + + + Defines the properties for HTTP route. + + + Gets the HTTP route. + The HTTP route. + + + Gets the URI that represents the virtual path of the current HTTP route. + The URI that represents the virtual path of the current HTTP route. + + + Defines an abstraction for resolving inline constraints as instances of . + + + Resolves the inline constraint. + The the inline constraint was resolved to. + The inline constraint to resolve. + + + Defines a route prefix. + + + Gets the route prefix. + The route prefix. + + + Represents a named route. + + + Initializes a new instance of the class. + The route name, if any; otherwise, null. + The route. + + + Gets the route name, if any; otherwise, null. + The route name, if any; otherwise, null. + + + Gets the route. + The route. + + + Represents an attribute route that may contain custom constraints. + + + Initializes a new instance of the class. + The route template. + + + Gets the route constraints, if any; otherwise null. + The route constraints, if any; otherwise null. + + + Creates the route entry + The created route entry. + The context. + + + Gets the route data tokens, if any; otherwise null. + The route data tokens, if any; otherwise null. + + + Gets the route defaults, if any; otherwise null. + The route defaults, if any; otherwise null. + + + Gets or sets the route name, if any; otherwise null. + The route name, if any; otherwise null. + + + Gets or sets the route order. + The route order. + + + Gets the route template. + The route template. + + + Represents a handler that specifies routing should not handle requests for a route template. When a route provides this class as a handler, requests matching against the route will be ignored. + + + Initializes a new instance of the class. + + + Represents a factory for creating URLs. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The HTTP request for this instance. + + + Creates an absolute URL using the specified path. + The generated URL. + The URL path, which may be a relative URL, a rooted URL, or a virtual path. + + + Returns a link for the specified route. + A link for the specified route. + The name of the route. + An object that contains the parameters for a route. + + + Returns a link for the specified route. + A link for the specified route. + The name of the route. + A route value. + + + Gets or sets the of the current instance. + The of the current instance. + + + Returns the route for the . + The route for the . + The name of the route. + A list of route values. + + + Returns the route for the . + The route for the . + The name of the route. + The route values. + + + Constrains a route parameter to contain only lowercase or uppercase letters A through Z in the English alphabet. + + + Initializes a new instance of the class. + + + Constrains a route parameter to represent only Boolean values. + + + Initializes a new instance of the class. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Constrains a route by several child constraints. + + + Initializes a new instance of the class. + The child constraints that must match for this constraint to match. + + + Gets the child constraints that must match for this constraint to match. + The child constraints that must match for this constraint to match. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Constrains a route parameter to represent only values. + + + Initializes a new instance of the class. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route of direction. + + + Constrains a route parameter to represent only decimal values. + + + Initializes a new instance of the class. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Constrains a route parameter to represent only 64-bit floating-point values. + + + + + Constrains a route parameter to represent only 32-bit floating-point values. + + + + + Constrains a route parameter to represent only values. + + + Initializes a new instance of the class. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Constrains a route parameter to represent only 32-bit integer values. + + + Initializes a new instance of the class. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Constrains a route parameter to be a string of a given length or within a given range of lengths. + + + + Initializes a new instance of the class that constrains a route parameter to be a string of a given length. + The minimum length of the route parameter. + The maximum length of the route parameter. + + + Gets the length of the route parameter, if one is set. + + + + Gets the maximum length of the route parameter, if one is set. + + + Gets the minimum length of the route parameter, if one is set. + + + Constrains a route parameter to represent only 64-bit integer values. + + + + + Constrains a route parameter to be a string with a maximum length. + + + Initializes a new instance of the class. + The maximum length. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Gets the maximum length of the route parameter. + The maximum length of the route parameter. + + + Constrains a route parameter to be an integer with a maximum value. + + + + + Gets the maximum value of the route parameter. + + + Constrains a route parameter to be a string with a maximum length. + + + Initializes a new instance of the class. + The minimum length. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Gets the minimum length of the route parameter. + The minimum length of the route parameter. + + + Constrains a route parameter to be a long with a minimum value. + + + Initializes a new instance of the class. + The minimum value of the route parameter. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Gets the minimum value of the route parameter. + The minimum value of the route parameter. + + + Constrains a route by an inner constraint that doesn't fail when an optional parameter is set to its default value. + + + Initializes a new instance of the class. + The inner constraint to match if the parameter is not an optional parameter without a value + + + Gets the inner constraint to match if the parameter is not an optional parameter without a value. + The inner constraint to match if the parameter is not an optional parameter without a value. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Constraints a route parameter to be an integer within a given range of values. + + + Initializes a new instance of the class. + The minimum value. + The maximum value. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Gets the maximum value of the route parameter. + The maximum value of the route parameter. + + + Gets the minimum value of the route parameter. + The minimum value of the route parameter. + + + Constrains a route parameter to match a regular expression. + + + Initializes a new instance of the class. + The pattern. + + + Determines whether this instance equals a specified route. + true if this instance equals a specified route; otherwise, false. + The request. + The route to compare. + The name of the parameter. + A list of parameter values. + The route direction. + + + Gets the regular expression pattern to match. + The regular expression pattern to match. + + + Provides a method for retrieving the innermost object of an object that might be wrapped by an <see cref="T:System.Web.Http.Services.IDecorator`1" />. + + + Gets the innermost object which does not implement <see cref="T:System.Web.Http.Services.IDecorator`1" />. + Object which needs to be unwrapped. + + + + Represents a container for service instances used by the . Note that this container only supports known types, and methods to get or set arbitrary service types will throw when called. For creation of arbitrary types, please use instead. The supported types for this container are: Passing any type which is not on this to any method on this interface will cause an to be thrown. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class with a specified object. + The object. + + + Removes a single-instance service from the default services. + The type of the service. + + + Gets a service of the specified type. + The first instance of the service, or null if the service is not found. + The type of service. + + + Gets the list of service objects for a given service type, and validates the service type. + The list of service objects of the specified type. + The service type. + + + Gets the list of service objects for a given service type. + The list of service objects of the specified type, or an empty list if the service is not found. + The type of service. + + + Queries whether a service type is single-instance. + true if the service type has at most one instance, or false if the service type supports multiple instances. + The service type. + + + Replaces a single-instance service object. + The service type. + The service object that replaces the previous instance. + + + Removes the cached values for a single service type. + The service type. + + + Defines a decorator that exposes the inner decorated object. + This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see . + + + Gets the inner object. + + + Represents a performance tracing class to log method entry/exit and duration. + + + Initializes the class with a specified configuration. + The configuration. + + + Represents the trace writer. + + + Invokes the specified traceAction to allow setting values in a new if and only if tracing is permitted at the given category and level. + The current . It may be null but doing so will prevent subsequent trace analysis from correlating the trace to a particular request. + The logical category for the trace. Users can define their own. + The at which to write this trace. + The action to invoke if tracing is enabled. The caller is expected to fill in the fields of the given in this action. + + + Represents an extension methods for . + + + Provides a set of methods and properties that help debug your code with the specified writer, request, category and exception. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The error occurred during execution. + + + Provides a set of methods and properties that help debug your code with the specified writer, request, category, exception, message format and argument. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The error occurred during execution. + The format of the message. + The message argument. + + + Provides a set of methods and properties that help debug your code with the specified writer, request, category, exception, message format and argument. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The format of the message. + The message argument. + + + Displays an error message in the list with the specified writer, request, category and exception. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The error occurred during execution. + + + Displays an error message in the list with the specified writer, request, category, exception, message format and argument. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The exception. + The format of the message. + The argument in the message. + + + Displays an error message in the list with the specified writer, request, category, message format and argument. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The format of the message. + The argument in the message. + + + Displays an error message in the class with the specified writer, request, category and exception. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The exception that appears during execution. + + + Displays an error message in the class with the specified writer, request, category and exception, message format and argument. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The exception. + The format of the message. + The message argument. + + + Displays an error message in the class with the specified writer, request, category and message format and argument. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The format of the message. + The message argument. + + + Displays the details in the . + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The error occurred during execution. + + + Displays the details in the . + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The error occurred during execution. + The format of the message. + The message argument. + + + Displays the details in the . + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The format of the message. + The message argument. + + + Indicates the trace listeners in the Listeners collection. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The trace level. + The error occurred during execution. + + + Indicates the trace listeners in the Listeners collection. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The trace level. + The error occurred during execution. + The format of the message. + The message argument. + + + Indicates the trace listeners in the Listeners collection. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The of the trace. + The format of the message. + The message argument. + + + Traces both a begin and an end trace around a specified operation. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The of the trace. + The name of the object performing the operation. It may be null. + The name of the operation being performed. It may be null. + The to invoke prior to performing the operation, allowing the given to be filled in. It may be null. + An <see cref="T:System.Func`1" /> that returns the that will perform the operation. + The to invoke after successfully performing the operation, allowing the given to be filled in. It may be null. + The to invoke if an error was encountered performing the operation, allowing the given to be filled in. It may be null. + + + Traces both a begin and an end trace around a specified operation. + The returned by the operation. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The of the trace. + The name of the object performing the operation. It may be null. + The name of the operation being performed. It may be null. + The to invoke prior to performing the operation, allowing the given to be filled in. It may be null. + An <see cref="T:System.Func`1" /> that returns the that will perform the operation. + The to invoke after successfully performing the operation, allowing the given to be filled in. The result of the completed task will also be passed to this action. This action may be null. + The to invoke if an error was encountered performing the operation, allowing the given to be filled in. It may be null. + The type of result produced by the . + + + Traces both a begin and an end trace around a specified operation. + The returned by the operation. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The of the trace. + The name of the object performing the operation. It may be null. + The name of the operation being performed. It may be null. + The to invoke prior to performing the operation, allowing the given to be filled in. It may be null. + An <see cref="T:System.Func`1" /> that returns the that will perform the operation. + The to invoke after successfully performing the operation, allowing the given to be filled in. It may be null. + The to invoke if an error was encountered performing the operation, allowing the given to be filled in. It may be null. + + + Indicates the warning level of execution. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The error occurred during execution. + + + Indicates the warning level of execution. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The error occurred during execution. + The format of the message. + The message argument. + + + Indicates the warning level of execution. + The . + The with which to associate the trace. It may be null. + The logical category of the trace. + The format of the message. + The message argument. + + + Specifies an enumeration of tracing categories. + + + An action category. + + + The controllers category. + + + The filters category. + + + The formatting category. + + + The message handlers category. + + + The model binding category. + + + The request category. + + + The routing category. + + + Specifies the kind of tracing operation. + + + Trace marking the beginning of some operation. + + + Trace marking the end of some operation. + + + Single trace, not part of a Begin/End trace pair. + + + Specifies an enumeration of tracing level. + + + Trace level for debugging traces. + + + Trace level for error traces. + + + Trace level for fatal traces. + + + Trace level for informational traces. + + + Tracing is disabled. + + + Trace level for warning traces. + + + Represents a trace record. + + + Initializes a new instance of the class. + The message request. + The trace category. + The trace level. + + + Gets or sets the tracing category. + The tracing category. + + + Gets or sets the exception. + The exception. + + + Gets or sets the kind of trace. + The kind of trace. + + + Gets or sets the tracing level. + The tracing level. + + + Gets or sets the message. + The message. + + + Gets or sets the logical operation name being performed. + The logical operation name being performed. + + + Gets or sets the logical name of the object performing the operation. + The logical name of the object performing the operation. + + + Gets the optional user-defined properties. + The optional user-defined properties. + + + Gets the from the record. + The from the record. + + + Gets the correlation ID from the . + The correlation ID from the . + + + Gets or sets the associated with the . + The associated with the . + + + Gets the of this trace (via ). + The of this trace (via ). + + + Represents a class used to recursively validate an object. + + + Initializes a new instance of the class. + + + Determines whether instances of a particular type should be validated. + true if the type should be validated; false otherwise. + The type to validate. + + + Determines whether the is valid and adds any validation errors to the 's . + true if model is valid, false otherwise. + The model to be validated. + The to use for validation. + The used to provide model metadata. + The within which the model is being validated. + The to append to the key for any validation errors. + + + Represents an interface for the validation of the models + + + Determines whether the model is valid and adds any validation errors to the actionContext's + trueif model is valid, false otherwise. + The model to be validated. + The to use for validation. + The used to provide the model metadata. + The within which the model is being validated. + The to append to the key for any validation errors. + + + This logs formatter errors to the provided . + + + Initializes a new instance of the class. + The model state. + The prefix. + + + Logs the specified model error. + The error path. + The error message. + + + Logs the specified model error. + The error path. + The error message. + + + Provides data for the event. + + + Initializes a new instance of the class. + The action context. + The parent node. + + + Gets or sets the context for an action. + The context for an action. + + + Gets or sets the parent of this node. + The parent of this node. + + + Provides data for the event. + + + Initializes a new instance of the class. + The action context. + The parent node. + + + Gets or sets the context for an action. + The context for an action. + + + Gets or sets the parent of this node. + The parent of this node. + + + Provides a container for model validation information. + + + Initializes a new instance of the class, using the model metadata and state key. + The model metadata. + The model state key. + + + Initializes a new instance of the class, using the model metadata, the model state key, and child model-validation nodes. + The model metadata. + The model state key. + The model child nodes. + + + Gets or sets the child nodes. + The child nodes. + + + Combines the current instance with a specified instance. + The model validation node to combine with the current instance. + + + Gets or sets the model metadata. + The model metadata. + + + Gets or sets the model state key. + The model state key. + + + Gets or sets a value that indicates whether validation should be suppressed. + true if validation should be suppressed; otherwise, false. + + + Validates the model using the specified execution context. + The action context. + + + Validates the model using the specified execution context and parent node. + The action context. + The parent node. + + + Gets or sets a value that indicates whether all properties of the model should be validated. + true if all properties of the model should be validated, or false if validation should be skipped. + + + Occurs when the model has been validated. + + + Occurs when the model is being validated. + + + Represents the selection of required members by checking for any required ModelValidators associated with the member. + + + Initializes a new instance of the class. + The metadata provider. + The validator providers. + + + Indicates whether the member is required for validation. + true if the member is required for validation; otherwise, false. + The member. + + + Provides a container for a validation result. + + + Initializes a new instance of the class. + + + Gets or sets the name of the member. + The name of the member. + + + Gets or sets the validation result message. + The validation result message. + + + Provides a base class for implementing validation logic. + + + Initializes a new instance of the class. + The validator providers. + + + Returns a composite model validator for the model. + A composite model validator for the model. + An enumeration of validator providers. + + + Gets a value that indicates whether a model property is required. + true if the model property is required; otherwise, false. + + + Validates a specified object. + A list of validation results. + The metadata. + The container. + + + Gets or sets an enumeration of validator providers. + An enumeration of validator providers. + + + Provides a list of validators for a model. + + + Initializes a new instance of the class. + + + Gets a list of validators associated with this . + The list of validators. + The metadata. + The validator providers. + + + Provides an abstract class for classes that implement a validation provider. + + + Initializes a new instance of the class. + + + Gets a type descriptor for the specified type. + A type descriptor for the specified type. + The type of the validation provider. + + + Gets the validators for the model using the metadata and validator providers. + The validators for the model. + The metadata. + An enumeration of validator providers. + + + Gets the validators for the model using the metadata, the validator providers, and a list of attributes. + The validators for the model. + The metadata. + An enumeration of validator providers. + The list of attributes. + + + Represents the method that creates a instance. + + + Represents an implementation of which providers validators for attributes which derive from . It also provides a validator for types which implement . To support client side validation, you can either register adapters through the static methods on this class, or by having your validation attributes implement . The logic to support IClientValidatable is implemented in . + + + Initializes a new instance of the class. + + + Gets the validators for the model using the specified metadata, validator provider and attributes. + The validators for the model. + The metadata. + The validator providers. + The attributes. + + + Registers an adapter to provide client-side validation. + The type of the validation attribute. + The type of the adapter. + + + Registers an adapter factory for the validation provider. + The type of the attribute. + The factory that will be used to create the object for the specified attribute. + + + Registers the default adapter. + The type of the adapter. + + + Registers the default adapter factory. + The factory that will be used to create the object for the default adapter. + + + Registers the default adapter type for objects which implement . The adapter type must derive from and it must contain a public constructor which takes two parameters of types and . + The type of the adapter. + + + Registers the default adapter factory for objects which implement . + The factory. + + + Registers an adapter type for the given modelType, which must implement . The adapter type must derive from and it must contain a public constructor which takes two parameters of types and . + The model type. + The type of the adapter. + + + Registers an adapter factory for the given modelType, which must implement . + The model type. + The factory. + + + Provides a factory for validators that are based on . + + + Represents a validator provider for data member model. + + + Initializes a new instance of the class. + + + Gets the validators for the model. + The validators for the model. + The metadata. + An enumerator of validator providers. + A list of attributes. + + + An implementation of which provides validators that throw exceptions when the model is invalid. + + + Initializes a new instance of the class. + + + Gets a list of validators associated with this . + The list of validators. + The metadata. + The validator providers. + The list of attributes. + + + Represents the provider for the required member model validator. + + + Initializes a new instance of the class. + The required member selector. + + + Gets the validator for the member model. + The validator for the member model. + The metadata. + The validator providers + + + Provides a model validator. + + + Initializes a new instance of the class. + The validator providers. + The validation attribute for the model. + + + Gets or sets the validation attribute for the model validator. + The validation attribute for the model validator. + + + Gets a value that indicates whether model validation is required. + true if model validation is required; otherwise, false. + + + Validates the model and returns the validation errors if any. + A list of validation error messages for the model, or an empty list if no errors have occurred. + The model metadata. + The container for the model. + + + A to represent an error. This validator will always throw an exception regardless of the actual model value. + + + Initializes a new instance of the class. + The list of model validator providers. + The error message for the exception. + + + Validates a specified object. + A list of validation results. + The metadata. + The container. + + + Represents the for required members. + + + Initializes a new instance of the class. + The validator providers. + + + Gets or sets a value that instructs the serialization engine that the member must be presents when validating. + true if the member is required; otherwise, false. + + + Validates the object. + A list of validation results. + The metadata. + The container. + + + Provides an object adapter that can be validated. + + + Initializes a new instance of the class. + The validation provider. + + + Validates the specified object. + A list of validation results. + The metadata. + The container. + + + Represents the base class for value providers whose values come from a collection that implements the interface. + + + Retrieves the keys from the specified . + The keys from the specified . + The prefix. + + + Represents an interface that is implemented by any that supports the creation of a to access the of an incoming . + + + Defines the methods that are required for a value provider in ASP.NET MVC. + + + Determines whether the collection contains the specified prefix. + true if the collection contains the specified prefix; otherwise, false. + The prefix to search for. + + + Retrieves a value object using the specified key. + The value object for the specified key, or null if the key is not found. + The key of the value object to retrieve. + + + This attribute is used to specify a custom . + + + Initializes a new instance of the . + The type of the model binder. + + + Initializes a new instance of the . + An array of model binder types. + + + Gets the value provider factories. + A collection of value provider factories. + A configuration object. + + + Gets the types of object returned by the value provider factory. + A collection of types. + + + Represents a factory for creating value-provider objects. + + + Initializes a new instance of the class. + + + Returns a value-provider object for the specified controller context. + A value-provider object. + An object that encapsulates information about the current HTTP request. + + + Represents the result of binding a value (such as from a form post or query string) to an action-method argument property, or to the argument itself. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The raw value. + The attempted value. + The culture. + + + Gets or sets the raw value that is converted to a string for display. + The raw value that is converted to a string for display. + + + Converts the value that is encapsulated by this result to the specified type. + The converted value. + The target type. + + + Converts the value that is encapsulated by this result to the specified type by using the specified culture information. + The converted value. + The target type. + The culture to use in the conversion. + + + Gets or sets the culture. + The culture. + + + Gets or set the raw value that is supplied by the value provider. + The raw value that is supplied by the value provider. + + + Represents a value provider whose values come from a list of value providers that implements the interface. + + + Initializes a new instance of the class. + + + Initializes a new instance of the class. + The list of value providers. + + + Determines whether the collection contains the specified . + true if the collection contains the specified ; otherwise, false. + The prefix to search for. + + + Retrieves the keys from the specified . + The keys from the specified . + The prefix from which keys are retrieved. + + + Retrieves a value object using the specified . + The value object for the specified . + The key of the value object to retrieve. + + + Inserts an element into the collection at the specified index. + The zero-based index at which should be inserted. + The object to insert. + + + Replaces the element at the specified index. + The zero-based index of the element to replace. + The new value for the element at the specified index. + + + Represents a factory for creating a list of value-provider objects. + + + Initializes a new instance of the class. + The collection of value-provider factories. + + + Retrieves a list of value-provider objects for the specified controller context. + The list of value-provider objects for the specified controller context. + An object that encapsulates information about the current HTTP request. + + + A value provider for name/value pairs. + + + + Initializes a new instance of the class. + The name/value pairs for the provider. + The culture used for the name/value pairs. + + + Initializes a new instance of the class, using a function delegate to provide the name/value pairs. + A function delegate that returns a collection of name/value pairs. + The culture used for the name/value pairs. + + + Determines whether the collection contains the specified prefix. + true if the collection contains the specified prefix; otherwise, false. + The prefix to search for. + + + Gets the keys from a prefix. + The keys. + The prefix. + + + Retrieves a value object using the specified key. + The value object for the specified key. + The key of the value object to retrieve. + + + Represents a value provider for query strings that are contained in a object. + + + Initializes a new instance of the class. + An object that encapsulates information about the current HTTP request. + An object that contains information about the target culture. + + + Represents a class that is responsible for creating a new instance of a query-string value-provider object. + + + Initializes a new instance of the class. + + + Retrieves a value-provider object for the specified controller context. + A query-string value-provider object. + An object that encapsulates information about the current HTTP request. + + + Represents a value provider for route data that is contained in an object that implements the IDictionary(Of TKey, TValue) interface. + + + Initializes a new instance of the class. + An object that contain information about the HTTP request. + An object that contains information about the target culture. + + + Represents a factory for creating route-data value provider objects. + + + Initializes a new instance of the class. + + + Retrieves a value-provider object for the specified controller context. + A value-provider object. + An object that encapsulates information about the current HTTP request. + + + \ No newline at end of file diff --git a/WizBot/bin/Debug/WizBot.exe.config b/WizBot/bin/Debug/WizBot.exe.config new file mode 100644 index 000000000..75d0347a5 --- /dev/null +++ b/WizBot/bin/Debug/WizBot.exe.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/WizBot/bin/Debug/WizBot.vshost.exe.config b/WizBot/bin/Debug/WizBot.vshost.exe.config new file mode 100644 index 000000000..75d0347a5 --- /dev/null +++ b/WizBot/bin/Debug/WizBot.vshost.exe.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/NadekoBot/bin/Debug/credentials_example.json b/WizBot/bin/Debug/credentials_example.json similarity index 92% rename from NadekoBot/bin/Debug/credentials_example.json rename to WizBot/bin/Debug/credentials_example.json index 5761a5f80..f9b8b8702 100644 --- a/NadekoBot/bin/Debug/credentials_example.json +++ b/WizBot/bin/Debug/credentials_example.json @@ -6,7 +6,7 @@ "GoogleAPIKey": "", "OwnerIds": [ 123123123123, - 5675675679845 + 99272781513920512 ], "TrelloAppKey": "", "SoundCloudClientID": "", diff --git a/NadekoBot/bin/Debug/data/8ball.json b/WizBot/bin/Debug/data/8ball.json similarity index 100% rename from NadekoBot/bin/Debug/data/8ball.json rename to WizBot/bin/Debug/data/8ball.json diff --git a/NadekoBot/bin/Debug/data/PokemonTypes.json b/WizBot/bin/Debug/data/PokemonTypes.json similarity index 99% rename from NadekoBot/bin/Debug/data/PokemonTypes.json rename to WizBot/bin/Debug/data/PokemonTypes.json index 0257a7024..6d7ac74a3 100644 --- a/NadekoBot/bin/Debug/data/PokemonTypes.json +++ b/WizBot/bin/Debug/data/PokemonTypes.json @@ -189,7 +189,7 @@ "vine whip", "razor leaf" ], - "Icon": "🌿" + "Icon": "🍃" }, { "Name": "ICE", diff --git a/WizBot/bin/Debug/data/ServerSpecificConfigs.json b/WizBot/bin/Debug/data/ServerSpecificConfigs.json new file mode 100644 index 000000000..84bacfcd7 --- /dev/null +++ b/WizBot/bin/Debug/data/ServerSpecificConfigs.json @@ -0,0 +1,9 @@ +{ + "128191554350219264": { + "VoicePlusTextEnabled": false, + "SendPrivateMessageOnMention": false, + "ListOfSelfAssignableRoles": [], + "AutoAssignedRole": 148111803618951169, + "ObservingStreams": [] + } +} \ No newline at end of file diff --git a/WizBot/bin/Debug/data/avatar.png b/WizBot/bin/Debug/data/avatar.png new file mode 100644 index 000000000..f36e855ee Binary files /dev/null and b/WizBot/bin/Debug/data/avatar.png differ diff --git a/WizBot/bin/Debug/data/config_example.json b/WizBot/bin/Debug/data/config_example.json new file mode 100644 index 000000000..18b1a362e --- /dev/null +++ b/WizBot/bin/Debug/data/config_example.json @@ -0,0 +1,135 @@ +{ + "DontJoinServers": false, + "ForwardMessages": true, + "IsRotatingStatus": false, + "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% neko": [ + "https://scontent.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/12750101_1313763215317390_1645972529_n.jpg?ig_cache_key=MTE4NzIzNDIzMzMxMTM3Njc0OQ%3D%3D.2.l", + "http://i117.photobucket.com/albums/o47/Nana-gloss/Neko/nekobible3.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/7a/9b/bd/7a9bbd1c3b16d2cfba0874974c9794bc.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/61/8b/b7/618bb7b7f61d2e3794846a6e6880ba47.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/bc/dc/4c/bcdc4cd8d0cc64a51eef1ee5c413cc72.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/ff/b2/43/ffb243bc5225426b9916a0fef21ed6ac.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/55/31/ac/5531aca0651dee8e60260617c6d28b39.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/89/0e/03/890e033cc3411f1b80d977aaffa81c98.jpg", + "https://s-media-cache-ak0.pinimg.com/564x/48/ee/78/48ee78d5fe31fb9dd1d2bc32df4159d7.jpg" + ], + "%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" + ] + }, + "RotatingStatuses": [], + "CommandPrefixes": { + "Administration": ".", + "Searches": "~", + "NSFW": "~", + "Conversations": "<@{0}>", + "ClashOfClans": ",", + "Help": "-", + "Music": "!m", + "Trello": "trello", + "Games": ">", + "Gambling": "$", + "Permissions": ";", + "Programming": "%", + "Pokemon": ">" + }, + "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": "WizFlower", + "DMHelpString": "Type `-h` for help." +} \ No newline at end of file diff --git a/NadekoBot/bin/Debug/data/currency_images/img1.jpg b/WizBot/bin/Debug/data/currency_images/img1.jpg similarity index 100% rename from NadekoBot/bin/Debug/data/currency_images/img1.jpg rename to WizBot/bin/Debug/data/currency_images/img1.jpg diff --git a/NadekoBot/bin/Debug/data/currency_images/img2.jpg b/WizBot/bin/Debug/data/currency_images/img2.jpg similarity index 100% rename from NadekoBot/bin/Debug/data/currency_images/img2.jpg rename to WizBot/bin/Debug/data/currency_images/img2.jpg diff --git a/NadekoBot/bin/Debug/data/currency_images/img3.jpg b/WizBot/bin/Debug/data/currency_images/img3.jpg similarity index 100% rename from NadekoBot/bin/Debug/data/currency_images/img3.jpg rename to WizBot/bin/Debug/data/currency_images/img3.jpg diff --git a/NadekoBot/bin/Debug/data/lol/_ERROR.png b/WizBot/bin/Debug/data/lol/_ERROR.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/_ERROR.png rename to WizBot/bin/Debug/data/lol/_ERROR.png diff --git a/NadekoBot/bin/Debug/data/lol/bg.png b/WizBot/bin/Debug/data/lol/bg.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/bg.png rename to WizBot/bin/Debug/data/lol/bg.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Aatrox.png b/WizBot/bin/Debug/data/lol/champions/Aatrox.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Aatrox.png rename to WizBot/bin/Debug/data/lol/champions/Aatrox.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ahri.png b/WizBot/bin/Debug/data/lol/champions/Ahri.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Ahri.png rename to WizBot/bin/Debug/data/lol/champions/Ahri.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Akali.png b/WizBot/bin/Debug/data/lol/champions/Akali.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Akali.png rename to WizBot/bin/Debug/data/lol/champions/Akali.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Alistar.png b/WizBot/bin/Debug/data/lol/champions/Alistar.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Alistar.png rename to WizBot/bin/Debug/data/lol/champions/Alistar.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Amumu.png b/WizBot/bin/Debug/data/lol/champions/Amumu.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Amumu.png rename to WizBot/bin/Debug/data/lol/champions/Amumu.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Anivia.png b/WizBot/bin/Debug/data/lol/champions/Anivia.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Anivia.png rename to WizBot/bin/Debug/data/lol/champions/Anivia.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Annie.png b/WizBot/bin/Debug/data/lol/champions/Annie.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Annie.png rename to WizBot/bin/Debug/data/lol/champions/Annie.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ashe.png b/WizBot/bin/Debug/data/lol/champions/Ashe.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Ashe.png rename to WizBot/bin/Debug/data/lol/champions/Ashe.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/AurelionSol.png b/WizBot/bin/Debug/data/lol/champions/AurelionSol.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/AurelionSol.png rename to WizBot/bin/Debug/data/lol/champions/AurelionSol.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Azir.png b/WizBot/bin/Debug/data/lol/champions/Azir.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Azir.png rename to WizBot/bin/Debug/data/lol/champions/Azir.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Bard.png b/WizBot/bin/Debug/data/lol/champions/Bard.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Bard.png rename to WizBot/bin/Debug/data/lol/champions/Bard.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Blitzcrank.png b/WizBot/bin/Debug/data/lol/champions/Blitzcrank.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Blitzcrank.png rename to WizBot/bin/Debug/data/lol/champions/Blitzcrank.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Brand.png b/WizBot/bin/Debug/data/lol/champions/Brand.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Brand.png rename to WizBot/bin/Debug/data/lol/champions/Brand.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Braum.png b/WizBot/bin/Debug/data/lol/champions/Braum.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Braum.png rename to WizBot/bin/Debug/data/lol/champions/Braum.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Caitlyn.png b/WizBot/bin/Debug/data/lol/champions/Caitlyn.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Caitlyn.png rename to WizBot/bin/Debug/data/lol/champions/Caitlyn.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Cassiopeia.png b/WizBot/bin/Debug/data/lol/champions/Cassiopeia.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Cassiopeia.png rename to WizBot/bin/Debug/data/lol/champions/Cassiopeia.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/ChoGath.png b/WizBot/bin/Debug/data/lol/champions/ChoGath.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/ChoGath.png rename to WizBot/bin/Debug/data/lol/champions/ChoGath.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Corki.png b/WizBot/bin/Debug/data/lol/champions/Corki.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Corki.png rename to WizBot/bin/Debug/data/lol/champions/Corki.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Darius.png b/WizBot/bin/Debug/data/lol/champions/Darius.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Darius.png rename to WizBot/bin/Debug/data/lol/champions/Darius.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Diana.png b/WizBot/bin/Debug/data/lol/champions/Diana.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Diana.png rename to WizBot/bin/Debug/data/lol/champions/Diana.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/DrMundo.png b/WizBot/bin/Debug/data/lol/champions/DrMundo.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/DrMundo.png rename to WizBot/bin/Debug/data/lol/champions/DrMundo.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Draven.png b/WizBot/bin/Debug/data/lol/champions/Draven.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Draven.png rename to WizBot/bin/Debug/data/lol/champions/Draven.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ekko.png b/WizBot/bin/Debug/data/lol/champions/Ekko.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Ekko.png rename to WizBot/bin/Debug/data/lol/champions/Ekko.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Elise.png b/WizBot/bin/Debug/data/lol/champions/Elise.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Elise.png rename to WizBot/bin/Debug/data/lol/champions/Elise.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Evelynn.png b/WizBot/bin/Debug/data/lol/champions/Evelynn.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Evelynn.png rename to WizBot/bin/Debug/data/lol/champions/Evelynn.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ezreal.png b/WizBot/bin/Debug/data/lol/champions/Ezreal.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Ezreal.png rename to WizBot/bin/Debug/data/lol/champions/Ezreal.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Fiddlesticks.png b/WizBot/bin/Debug/data/lol/champions/Fiddlesticks.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Fiddlesticks.png rename to WizBot/bin/Debug/data/lol/champions/Fiddlesticks.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Fiora.png b/WizBot/bin/Debug/data/lol/champions/Fiora.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Fiora.png rename to WizBot/bin/Debug/data/lol/champions/Fiora.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Fizz.png b/WizBot/bin/Debug/data/lol/champions/Fizz.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Fizz.png rename to WizBot/bin/Debug/data/lol/champions/Fizz.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Galio.png b/WizBot/bin/Debug/data/lol/champions/Galio.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Galio.png rename to WizBot/bin/Debug/data/lol/champions/Galio.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Gangplank.png b/WizBot/bin/Debug/data/lol/champions/Gangplank.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Gangplank.png rename to WizBot/bin/Debug/data/lol/champions/Gangplank.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Garen.png b/WizBot/bin/Debug/data/lol/champions/Garen.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Garen.png rename to WizBot/bin/Debug/data/lol/champions/Garen.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Gnar.png b/WizBot/bin/Debug/data/lol/champions/Gnar.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Gnar.png rename to WizBot/bin/Debug/data/lol/champions/Gnar.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Gragas.png b/WizBot/bin/Debug/data/lol/champions/Gragas.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Gragas.png rename to WizBot/bin/Debug/data/lol/champions/Gragas.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Graves.png b/WizBot/bin/Debug/data/lol/champions/Graves.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Graves.png rename to WizBot/bin/Debug/data/lol/champions/Graves.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Hecarim.png b/WizBot/bin/Debug/data/lol/champions/Hecarim.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Hecarim.png rename to WizBot/bin/Debug/data/lol/champions/Hecarim.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Heimerdinger.png b/WizBot/bin/Debug/data/lol/champions/Heimerdinger.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Heimerdinger.png rename to WizBot/bin/Debug/data/lol/champions/Heimerdinger.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Illaoi.png b/WizBot/bin/Debug/data/lol/champions/Illaoi.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Illaoi.png rename to WizBot/bin/Debug/data/lol/champions/Illaoi.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Irelia.png b/WizBot/bin/Debug/data/lol/champions/Irelia.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Irelia.png rename to WizBot/bin/Debug/data/lol/champions/Irelia.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Janna.png b/WizBot/bin/Debug/data/lol/champions/Janna.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Janna.png rename to WizBot/bin/Debug/data/lol/champions/Janna.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/JarvanIV.png b/WizBot/bin/Debug/data/lol/champions/JarvanIV.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/JarvanIV.png rename to WizBot/bin/Debug/data/lol/champions/JarvanIV.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Jax.png b/WizBot/bin/Debug/data/lol/champions/Jax.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Jax.png rename to WizBot/bin/Debug/data/lol/champions/Jax.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Jayce.png b/WizBot/bin/Debug/data/lol/champions/Jayce.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Jayce.png rename to WizBot/bin/Debug/data/lol/champions/Jayce.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Jhin.png b/WizBot/bin/Debug/data/lol/champions/Jhin.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Jhin.png rename to WizBot/bin/Debug/data/lol/champions/Jhin.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Jinx.png b/WizBot/bin/Debug/data/lol/champions/Jinx.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Jinx.png rename to WizBot/bin/Debug/data/lol/champions/Jinx.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kalista.png b/WizBot/bin/Debug/data/lol/champions/Kalista.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Kalista.png rename to WizBot/bin/Debug/data/lol/champions/Kalista.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Karma.png b/WizBot/bin/Debug/data/lol/champions/Karma.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Karma.png rename to WizBot/bin/Debug/data/lol/champions/Karma.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Karthus.png b/WizBot/bin/Debug/data/lol/champions/Karthus.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Karthus.png rename to WizBot/bin/Debug/data/lol/champions/Karthus.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kassadin.png b/WizBot/bin/Debug/data/lol/champions/Kassadin.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Kassadin.png rename to WizBot/bin/Debug/data/lol/champions/Kassadin.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Katarina.png b/WizBot/bin/Debug/data/lol/champions/Katarina.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Katarina.png rename to WizBot/bin/Debug/data/lol/champions/Katarina.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kayle.png b/WizBot/bin/Debug/data/lol/champions/Kayle.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Kayle.png rename to WizBot/bin/Debug/data/lol/champions/Kayle.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kennen.png b/WizBot/bin/Debug/data/lol/champions/Kennen.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Kennen.png rename to WizBot/bin/Debug/data/lol/champions/Kennen.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/KhaZix.png b/WizBot/bin/Debug/data/lol/champions/KhaZix.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/KhaZix.png rename to WizBot/bin/Debug/data/lol/champions/KhaZix.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Kindred.png b/WizBot/bin/Debug/data/lol/champions/Kindred.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Kindred.png rename to WizBot/bin/Debug/data/lol/champions/Kindred.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/KogMaw.png b/WizBot/bin/Debug/data/lol/champions/KogMaw.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/KogMaw.png rename to WizBot/bin/Debug/data/lol/champions/KogMaw.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/LeBlanc.png b/WizBot/bin/Debug/data/lol/champions/LeBlanc.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/LeBlanc.png rename to WizBot/bin/Debug/data/lol/champions/LeBlanc.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/LeeSin.png b/WizBot/bin/Debug/data/lol/champions/LeeSin.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/LeeSin.png rename to WizBot/bin/Debug/data/lol/champions/LeeSin.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Leona.png b/WizBot/bin/Debug/data/lol/champions/Leona.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Leona.png rename to WizBot/bin/Debug/data/lol/champions/Leona.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Lissandra.png b/WizBot/bin/Debug/data/lol/champions/Lissandra.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Lissandra.png rename to WizBot/bin/Debug/data/lol/champions/Lissandra.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Lucian.png b/WizBot/bin/Debug/data/lol/champions/Lucian.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Lucian.png rename to WizBot/bin/Debug/data/lol/champions/Lucian.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Lulu.png b/WizBot/bin/Debug/data/lol/champions/Lulu.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Lulu.png rename to WizBot/bin/Debug/data/lol/champions/Lulu.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Lux.png b/WizBot/bin/Debug/data/lol/champions/Lux.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Lux.png rename to WizBot/bin/Debug/data/lol/champions/Lux.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Malphite.png b/WizBot/bin/Debug/data/lol/champions/Malphite.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Malphite.png rename to WizBot/bin/Debug/data/lol/champions/Malphite.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Malzahar.png b/WizBot/bin/Debug/data/lol/champions/Malzahar.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Malzahar.png rename to WizBot/bin/Debug/data/lol/champions/Malzahar.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Maokai.png b/WizBot/bin/Debug/data/lol/champions/Maokai.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Maokai.png rename to WizBot/bin/Debug/data/lol/champions/Maokai.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/MasterYi.png b/WizBot/bin/Debug/data/lol/champions/MasterYi.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/MasterYi.png rename to WizBot/bin/Debug/data/lol/champions/MasterYi.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/MissFortune.png b/WizBot/bin/Debug/data/lol/champions/MissFortune.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/MissFortune.png rename to WizBot/bin/Debug/data/lol/champions/MissFortune.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/MonkeyKing.png b/WizBot/bin/Debug/data/lol/champions/MonkeyKing.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/MonkeyKing.png rename to WizBot/bin/Debug/data/lol/champions/MonkeyKing.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Mordekaiser.png b/WizBot/bin/Debug/data/lol/champions/Mordekaiser.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Mordekaiser.png rename to WizBot/bin/Debug/data/lol/champions/Mordekaiser.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Morgana.png b/WizBot/bin/Debug/data/lol/champions/Morgana.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Morgana.png rename to WizBot/bin/Debug/data/lol/champions/Morgana.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nami.png b/WizBot/bin/Debug/data/lol/champions/Nami.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Nami.png rename to WizBot/bin/Debug/data/lol/champions/Nami.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nasus.png b/WizBot/bin/Debug/data/lol/champions/Nasus.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Nasus.png rename to WizBot/bin/Debug/data/lol/champions/Nasus.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nautilus.png b/WizBot/bin/Debug/data/lol/champions/Nautilus.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Nautilus.png rename to WizBot/bin/Debug/data/lol/champions/Nautilus.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nidalee.png b/WizBot/bin/Debug/data/lol/champions/Nidalee.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Nidalee.png rename to WizBot/bin/Debug/data/lol/champions/Nidalee.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nocturne.png b/WizBot/bin/Debug/data/lol/champions/Nocturne.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Nocturne.png rename to WizBot/bin/Debug/data/lol/champions/Nocturne.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Nunu.png b/WizBot/bin/Debug/data/lol/champions/Nunu.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Nunu.png rename to WizBot/bin/Debug/data/lol/champions/Nunu.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Olaf.png b/WizBot/bin/Debug/data/lol/champions/Olaf.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Olaf.png rename to WizBot/bin/Debug/data/lol/champions/Olaf.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Orianna.png b/WizBot/bin/Debug/data/lol/champions/Orianna.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Orianna.png rename to WizBot/bin/Debug/data/lol/champions/Orianna.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Pantheon.png b/WizBot/bin/Debug/data/lol/champions/Pantheon.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Pantheon.png rename to WizBot/bin/Debug/data/lol/champions/Pantheon.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Poppy.png b/WizBot/bin/Debug/data/lol/champions/Poppy.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Poppy.png rename to WizBot/bin/Debug/data/lol/champions/Poppy.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Quinn.png b/WizBot/bin/Debug/data/lol/champions/Quinn.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Quinn.png rename to WizBot/bin/Debug/data/lol/champions/Quinn.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Rammus.png b/WizBot/bin/Debug/data/lol/champions/Rammus.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Rammus.png rename to WizBot/bin/Debug/data/lol/champions/Rammus.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/RekSai.png b/WizBot/bin/Debug/data/lol/champions/RekSai.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/RekSai.png rename to WizBot/bin/Debug/data/lol/champions/RekSai.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Renekton.png b/WizBot/bin/Debug/data/lol/champions/Renekton.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Renekton.png rename to WizBot/bin/Debug/data/lol/champions/Renekton.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Rengar.png b/WizBot/bin/Debug/data/lol/champions/Rengar.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Rengar.png rename to WizBot/bin/Debug/data/lol/champions/Rengar.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Riven.png b/WizBot/bin/Debug/data/lol/champions/Riven.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Riven.png rename to WizBot/bin/Debug/data/lol/champions/Riven.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Rumble.png b/WizBot/bin/Debug/data/lol/champions/Rumble.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Rumble.png rename to WizBot/bin/Debug/data/lol/champions/Rumble.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ryze.png b/WizBot/bin/Debug/data/lol/champions/Ryze.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Ryze.png rename to WizBot/bin/Debug/data/lol/champions/Ryze.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Sejuani.png b/WizBot/bin/Debug/data/lol/champions/Sejuani.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Sejuani.png rename to WizBot/bin/Debug/data/lol/champions/Sejuani.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Shaco.png b/WizBot/bin/Debug/data/lol/champions/Shaco.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Shaco.png rename to WizBot/bin/Debug/data/lol/champions/Shaco.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Shen.png b/WizBot/bin/Debug/data/lol/champions/Shen.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Shen.png rename to WizBot/bin/Debug/data/lol/champions/Shen.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Shyvana.png b/WizBot/bin/Debug/data/lol/champions/Shyvana.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Shyvana.png rename to WizBot/bin/Debug/data/lol/champions/Shyvana.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Singed.png b/WizBot/bin/Debug/data/lol/champions/Singed.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Singed.png rename to WizBot/bin/Debug/data/lol/champions/Singed.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Sion.png b/WizBot/bin/Debug/data/lol/champions/Sion.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Sion.png rename to WizBot/bin/Debug/data/lol/champions/Sion.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Sivir.png b/WizBot/bin/Debug/data/lol/champions/Sivir.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Sivir.png rename to WizBot/bin/Debug/data/lol/champions/Sivir.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Skarner.png b/WizBot/bin/Debug/data/lol/champions/Skarner.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Skarner.png rename to WizBot/bin/Debug/data/lol/champions/Skarner.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Sona.png b/WizBot/bin/Debug/data/lol/champions/Sona.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Sona.png rename to WizBot/bin/Debug/data/lol/champions/Sona.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Soraka.png b/WizBot/bin/Debug/data/lol/champions/Soraka.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Soraka.png rename to WizBot/bin/Debug/data/lol/champions/Soraka.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Swain.png b/WizBot/bin/Debug/data/lol/champions/Swain.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Swain.png rename to WizBot/bin/Debug/data/lol/champions/Swain.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Syndra.png b/WizBot/bin/Debug/data/lol/champions/Syndra.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Syndra.png rename to WizBot/bin/Debug/data/lol/champions/Syndra.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/TahmKench.png b/WizBot/bin/Debug/data/lol/champions/TahmKench.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/TahmKench.png rename to WizBot/bin/Debug/data/lol/champions/TahmKench.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Talon.png b/WizBot/bin/Debug/data/lol/champions/Talon.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Talon.png rename to WizBot/bin/Debug/data/lol/champions/Talon.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Taric.png b/WizBot/bin/Debug/data/lol/champions/Taric.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Taric.png rename to WizBot/bin/Debug/data/lol/champions/Taric.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Teemo.png b/WizBot/bin/Debug/data/lol/champions/Teemo.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Teemo.png rename to WizBot/bin/Debug/data/lol/champions/Teemo.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Thresh.png b/WizBot/bin/Debug/data/lol/champions/Thresh.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Thresh.png rename to WizBot/bin/Debug/data/lol/champions/Thresh.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Tristana.png b/WizBot/bin/Debug/data/lol/champions/Tristana.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Tristana.png rename to WizBot/bin/Debug/data/lol/champions/Tristana.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Trundle.png b/WizBot/bin/Debug/data/lol/champions/Trundle.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Trundle.png rename to WizBot/bin/Debug/data/lol/champions/Trundle.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Tryndamere.png b/WizBot/bin/Debug/data/lol/champions/Tryndamere.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Tryndamere.png rename to WizBot/bin/Debug/data/lol/champions/Tryndamere.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/TwistedFate.png b/WizBot/bin/Debug/data/lol/champions/TwistedFate.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/TwistedFate.png rename to WizBot/bin/Debug/data/lol/champions/TwistedFate.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Twitch.png b/WizBot/bin/Debug/data/lol/champions/Twitch.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Twitch.png rename to WizBot/bin/Debug/data/lol/champions/Twitch.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Udyr.png b/WizBot/bin/Debug/data/lol/champions/Udyr.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Udyr.png rename to WizBot/bin/Debug/data/lol/champions/Udyr.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Urgot.png b/WizBot/bin/Debug/data/lol/champions/Urgot.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Urgot.png rename to WizBot/bin/Debug/data/lol/champions/Urgot.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Varus.png b/WizBot/bin/Debug/data/lol/champions/Varus.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Varus.png rename to WizBot/bin/Debug/data/lol/champions/Varus.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Vayne.png b/WizBot/bin/Debug/data/lol/champions/Vayne.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Vayne.png rename to WizBot/bin/Debug/data/lol/champions/Vayne.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Veigar.png b/WizBot/bin/Debug/data/lol/champions/Veigar.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Veigar.png rename to WizBot/bin/Debug/data/lol/champions/Veigar.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Velkoz.png b/WizBot/bin/Debug/data/lol/champions/Velkoz.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Velkoz.png rename to WizBot/bin/Debug/data/lol/champions/Velkoz.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Vi.png b/WizBot/bin/Debug/data/lol/champions/Vi.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Vi.png rename to WizBot/bin/Debug/data/lol/champions/Vi.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Viktor.png b/WizBot/bin/Debug/data/lol/champions/Viktor.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Viktor.png rename to WizBot/bin/Debug/data/lol/champions/Viktor.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Vladimir.png b/WizBot/bin/Debug/data/lol/champions/Vladimir.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Vladimir.png rename to WizBot/bin/Debug/data/lol/champions/Vladimir.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Volibear.png b/WizBot/bin/Debug/data/lol/champions/Volibear.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Volibear.png rename to WizBot/bin/Debug/data/lol/champions/Volibear.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Warwick.png b/WizBot/bin/Debug/data/lol/champions/Warwick.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Warwick.png rename to WizBot/bin/Debug/data/lol/champions/Warwick.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Xerath.png b/WizBot/bin/Debug/data/lol/champions/Xerath.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Xerath.png rename to WizBot/bin/Debug/data/lol/champions/Xerath.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/XinZhao.png b/WizBot/bin/Debug/data/lol/champions/XinZhao.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/XinZhao.png rename to WizBot/bin/Debug/data/lol/champions/XinZhao.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Yasuo.png b/WizBot/bin/Debug/data/lol/champions/Yasuo.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Yasuo.png rename to WizBot/bin/Debug/data/lol/champions/Yasuo.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Yorick.png b/WizBot/bin/Debug/data/lol/champions/Yorick.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Yorick.png rename to WizBot/bin/Debug/data/lol/champions/Yorick.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Zac.png b/WizBot/bin/Debug/data/lol/champions/Zac.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Zac.png rename to WizBot/bin/Debug/data/lol/champions/Zac.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Zed.png b/WizBot/bin/Debug/data/lol/champions/Zed.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Zed.png rename to WizBot/bin/Debug/data/lol/champions/Zed.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Ziggs.png b/WizBot/bin/Debug/data/lol/champions/Ziggs.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Ziggs.png rename to WizBot/bin/Debug/data/lol/champions/Ziggs.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Zilean.png b/WizBot/bin/Debug/data/lol/champions/Zilean.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Zilean.png rename to WizBot/bin/Debug/data/lol/champions/Zilean.png diff --git a/NadekoBot/bin/Debug/data/lol/champions/Zyra.png b/WizBot/bin/Debug/data/lol/champions/Zyra.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/champions/Zyra.png rename to WizBot/bin/Debug/data/lol/champions/Zyra.png diff --git a/NadekoBot/bin/Debug/data/lol/ex1.png b/WizBot/bin/Debug/data/lol/ex1.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/ex1.png rename to WizBot/bin/Debug/data/lol/ex1.png diff --git a/NadekoBot/bin/Debug/data/lol/ex2.png b/WizBot/bin/Debug/data/lol/ex2.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/ex2.png rename to WizBot/bin/Debug/data/lol/ex2.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1001.png b/WizBot/bin/Debug/data/lol/items/1001.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1001.png rename to WizBot/bin/Debug/data/lol/items/1001.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1004.png b/WizBot/bin/Debug/data/lol/items/1004.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1004.png rename to WizBot/bin/Debug/data/lol/items/1004.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1006.png b/WizBot/bin/Debug/data/lol/items/1006.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1006.png rename to WizBot/bin/Debug/data/lol/items/1006.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1011.png b/WizBot/bin/Debug/data/lol/items/1011.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1011.png rename to WizBot/bin/Debug/data/lol/items/1011.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1018.png b/WizBot/bin/Debug/data/lol/items/1018.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1018.png rename to WizBot/bin/Debug/data/lol/items/1018.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1026.png b/WizBot/bin/Debug/data/lol/items/1026.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1026.png rename to WizBot/bin/Debug/data/lol/items/1026.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1027.png b/WizBot/bin/Debug/data/lol/items/1027.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1027.png rename to WizBot/bin/Debug/data/lol/items/1027.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1028.png b/WizBot/bin/Debug/data/lol/items/1028.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1028.png rename to WizBot/bin/Debug/data/lol/items/1028.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1029.png b/WizBot/bin/Debug/data/lol/items/1029.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1029.png rename to WizBot/bin/Debug/data/lol/items/1029.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1031.png b/WizBot/bin/Debug/data/lol/items/1031.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1031.png rename to WizBot/bin/Debug/data/lol/items/1031.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1033.png b/WizBot/bin/Debug/data/lol/items/1033.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1033.png rename to WizBot/bin/Debug/data/lol/items/1033.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1036.png b/WizBot/bin/Debug/data/lol/items/1036.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1036.png rename to WizBot/bin/Debug/data/lol/items/1036.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1037.png b/WizBot/bin/Debug/data/lol/items/1037.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1037.png rename to WizBot/bin/Debug/data/lol/items/1037.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1038.png b/WizBot/bin/Debug/data/lol/items/1038.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1038.png rename to WizBot/bin/Debug/data/lol/items/1038.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1039.png b/WizBot/bin/Debug/data/lol/items/1039.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1039.png rename to WizBot/bin/Debug/data/lol/items/1039.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1041.png b/WizBot/bin/Debug/data/lol/items/1041.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1041.png rename to WizBot/bin/Debug/data/lol/items/1041.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1042.png b/WizBot/bin/Debug/data/lol/items/1042.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1042.png rename to WizBot/bin/Debug/data/lol/items/1042.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1043.png b/WizBot/bin/Debug/data/lol/items/1043.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1043.png rename to WizBot/bin/Debug/data/lol/items/1043.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1051.png b/WizBot/bin/Debug/data/lol/items/1051.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1051.png rename to WizBot/bin/Debug/data/lol/items/1051.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1052.png b/WizBot/bin/Debug/data/lol/items/1052.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1052.png rename to WizBot/bin/Debug/data/lol/items/1052.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1053.png b/WizBot/bin/Debug/data/lol/items/1053.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1053.png rename to WizBot/bin/Debug/data/lol/items/1053.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1054.png b/WizBot/bin/Debug/data/lol/items/1054.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1054.png rename to WizBot/bin/Debug/data/lol/items/1054.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1055.png b/WizBot/bin/Debug/data/lol/items/1055.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1055.png rename to WizBot/bin/Debug/data/lol/items/1055.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1056.png b/WizBot/bin/Debug/data/lol/items/1056.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1056.png rename to WizBot/bin/Debug/data/lol/items/1056.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1057.png b/WizBot/bin/Debug/data/lol/items/1057.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1057.png rename to WizBot/bin/Debug/data/lol/items/1057.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1058.png b/WizBot/bin/Debug/data/lol/items/1058.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1058.png rename to WizBot/bin/Debug/data/lol/items/1058.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1062.png b/WizBot/bin/Debug/data/lol/items/1062.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1062.png rename to WizBot/bin/Debug/data/lol/items/1062.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1063.png b/WizBot/bin/Debug/data/lol/items/1063.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1063.png rename to WizBot/bin/Debug/data/lol/items/1063.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1082.png b/WizBot/bin/Debug/data/lol/items/1082.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1082.png rename to WizBot/bin/Debug/data/lol/items/1082.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1083.png b/WizBot/bin/Debug/data/lol/items/1083.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1083.png rename to WizBot/bin/Debug/data/lol/items/1083.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1300.png b/WizBot/bin/Debug/data/lol/items/1300.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1300.png rename to WizBot/bin/Debug/data/lol/items/1300.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1301.png b/WizBot/bin/Debug/data/lol/items/1301.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1301.png rename to WizBot/bin/Debug/data/lol/items/1301.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1302.png b/WizBot/bin/Debug/data/lol/items/1302.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1302.png rename to WizBot/bin/Debug/data/lol/items/1302.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1303.png b/WizBot/bin/Debug/data/lol/items/1303.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1303.png rename to WizBot/bin/Debug/data/lol/items/1303.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1305.png b/WizBot/bin/Debug/data/lol/items/1305.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1305.png rename to WizBot/bin/Debug/data/lol/items/1305.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1306.png b/WizBot/bin/Debug/data/lol/items/1306.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1306.png rename to WizBot/bin/Debug/data/lol/items/1306.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1307.png b/WizBot/bin/Debug/data/lol/items/1307.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1307.png rename to WizBot/bin/Debug/data/lol/items/1307.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1308.png b/WizBot/bin/Debug/data/lol/items/1308.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1308.png rename to WizBot/bin/Debug/data/lol/items/1308.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1310.png b/WizBot/bin/Debug/data/lol/items/1310.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1310.png rename to WizBot/bin/Debug/data/lol/items/1310.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1311.png b/WizBot/bin/Debug/data/lol/items/1311.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1311.png rename to WizBot/bin/Debug/data/lol/items/1311.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1312.png b/WizBot/bin/Debug/data/lol/items/1312.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1312.png rename to WizBot/bin/Debug/data/lol/items/1312.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1313.png b/WizBot/bin/Debug/data/lol/items/1313.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1313.png rename to WizBot/bin/Debug/data/lol/items/1313.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1315.png b/WizBot/bin/Debug/data/lol/items/1315.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1315.png rename to WizBot/bin/Debug/data/lol/items/1315.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1316.png b/WizBot/bin/Debug/data/lol/items/1316.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1316.png rename to WizBot/bin/Debug/data/lol/items/1316.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1317.png b/WizBot/bin/Debug/data/lol/items/1317.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1317.png rename to WizBot/bin/Debug/data/lol/items/1317.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1318.png b/WizBot/bin/Debug/data/lol/items/1318.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1318.png rename to WizBot/bin/Debug/data/lol/items/1318.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1320.png b/WizBot/bin/Debug/data/lol/items/1320.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1320.png rename to WizBot/bin/Debug/data/lol/items/1320.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1321.png b/WizBot/bin/Debug/data/lol/items/1321.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1321.png rename to WizBot/bin/Debug/data/lol/items/1321.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1322.png b/WizBot/bin/Debug/data/lol/items/1322.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1322.png rename to WizBot/bin/Debug/data/lol/items/1322.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1323.png b/WizBot/bin/Debug/data/lol/items/1323.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1323.png rename to WizBot/bin/Debug/data/lol/items/1323.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1325.png b/WizBot/bin/Debug/data/lol/items/1325.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1325.png rename to WizBot/bin/Debug/data/lol/items/1325.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1326.png b/WizBot/bin/Debug/data/lol/items/1326.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1326.png rename to WizBot/bin/Debug/data/lol/items/1326.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1327.png b/WizBot/bin/Debug/data/lol/items/1327.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1327.png rename to WizBot/bin/Debug/data/lol/items/1327.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1328.png b/WizBot/bin/Debug/data/lol/items/1328.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1328.png rename to WizBot/bin/Debug/data/lol/items/1328.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1330.png b/WizBot/bin/Debug/data/lol/items/1330.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1330.png rename to WizBot/bin/Debug/data/lol/items/1330.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1331.png b/WizBot/bin/Debug/data/lol/items/1331.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1331.png rename to WizBot/bin/Debug/data/lol/items/1331.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1332.png b/WizBot/bin/Debug/data/lol/items/1332.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1332.png rename to WizBot/bin/Debug/data/lol/items/1332.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1333.png b/WizBot/bin/Debug/data/lol/items/1333.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1333.png rename to WizBot/bin/Debug/data/lol/items/1333.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1400.png b/WizBot/bin/Debug/data/lol/items/1400.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1400.png rename to WizBot/bin/Debug/data/lol/items/1400.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1401.png b/WizBot/bin/Debug/data/lol/items/1401.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1401.png rename to WizBot/bin/Debug/data/lol/items/1401.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1402.png b/WizBot/bin/Debug/data/lol/items/1402.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1402.png rename to WizBot/bin/Debug/data/lol/items/1402.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1403.png b/WizBot/bin/Debug/data/lol/items/1403.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1403.png rename to WizBot/bin/Debug/data/lol/items/1403.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1404.png b/WizBot/bin/Debug/data/lol/items/1404.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1404.png rename to WizBot/bin/Debug/data/lol/items/1404.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1405.png b/WizBot/bin/Debug/data/lol/items/1405.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1405.png rename to WizBot/bin/Debug/data/lol/items/1405.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1406.png b/WizBot/bin/Debug/data/lol/items/1406.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1406.png rename to WizBot/bin/Debug/data/lol/items/1406.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1407.png b/WizBot/bin/Debug/data/lol/items/1407.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1407.png rename to WizBot/bin/Debug/data/lol/items/1407.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1408.png b/WizBot/bin/Debug/data/lol/items/1408.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1408.png rename to WizBot/bin/Debug/data/lol/items/1408.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1409.png b/WizBot/bin/Debug/data/lol/items/1409.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1409.png rename to WizBot/bin/Debug/data/lol/items/1409.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1410.png b/WizBot/bin/Debug/data/lol/items/1410.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1410.png rename to WizBot/bin/Debug/data/lol/items/1410.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1411.png b/WizBot/bin/Debug/data/lol/items/1411.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1411.png rename to WizBot/bin/Debug/data/lol/items/1411.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1412.png b/WizBot/bin/Debug/data/lol/items/1412.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1412.png rename to WizBot/bin/Debug/data/lol/items/1412.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1413.png b/WizBot/bin/Debug/data/lol/items/1413.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1413.png rename to WizBot/bin/Debug/data/lol/items/1413.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1414.png b/WizBot/bin/Debug/data/lol/items/1414.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1414.png rename to WizBot/bin/Debug/data/lol/items/1414.png diff --git a/NadekoBot/bin/Debug/data/lol/items/1415.png b/WizBot/bin/Debug/data/lol/items/1415.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/1415.png rename to WizBot/bin/Debug/data/lol/items/1415.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2003.png b/WizBot/bin/Debug/data/lol/items/2003.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2003.png rename to WizBot/bin/Debug/data/lol/items/2003.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2009.png b/WizBot/bin/Debug/data/lol/items/2009.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2009.png rename to WizBot/bin/Debug/data/lol/items/2009.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2010.png b/WizBot/bin/Debug/data/lol/items/2010.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2010.png rename to WizBot/bin/Debug/data/lol/items/2010.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2015.png b/WizBot/bin/Debug/data/lol/items/2015.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2015.png rename to WizBot/bin/Debug/data/lol/items/2015.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2031.png b/WizBot/bin/Debug/data/lol/items/2031.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2031.png rename to WizBot/bin/Debug/data/lol/items/2031.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2032.png b/WizBot/bin/Debug/data/lol/items/2032.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2032.png rename to WizBot/bin/Debug/data/lol/items/2032.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2033.png b/WizBot/bin/Debug/data/lol/items/2033.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2033.png rename to WizBot/bin/Debug/data/lol/items/2033.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2043.png b/WizBot/bin/Debug/data/lol/items/2043.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2043.png rename to WizBot/bin/Debug/data/lol/items/2043.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2045.png b/WizBot/bin/Debug/data/lol/items/2045.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2045.png rename to WizBot/bin/Debug/data/lol/items/2045.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2047.png b/WizBot/bin/Debug/data/lol/items/2047.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2047.png rename to WizBot/bin/Debug/data/lol/items/2047.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2049.png b/WizBot/bin/Debug/data/lol/items/2049.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2049.png rename to WizBot/bin/Debug/data/lol/items/2049.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2050.png b/WizBot/bin/Debug/data/lol/items/2050.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2050.png rename to WizBot/bin/Debug/data/lol/items/2050.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2051.png b/WizBot/bin/Debug/data/lol/items/2051.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2051.png rename to WizBot/bin/Debug/data/lol/items/2051.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2052.png b/WizBot/bin/Debug/data/lol/items/2052.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2052.png rename to WizBot/bin/Debug/data/lol/items/2052.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2053.png b/WizBot/bin/Debug/data/lol/items/2053.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2053.png rename to WizBot/bin/Debug/data/lol/items/2053.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2054.png b/WizBot/bin/Debug/data/lol/items/2054.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2054.png rename to WizBot/bin/Debug/data/lol/items/2054.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2138.png b/WizBot/bin/Debug/data/lol/items/2138.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2138.png rename to WizBot/bin/Debug/data/lol/items/2138.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2139.png b/WizBot/bin/Debug/data/lol/items/2139.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2139.png rename to WizBot/bin/Debug/data/lol/items/2139.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2140.png b/WizBot/bin/Debug/data/lol/items/2140.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2140.png rename to WizBot/bin/Debug/data/lol/items/2140.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2301.png b/WizBot/bin/Debug/data/lol/items/2301.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2301.png rename to WizBot/bin/Debug/data/lol/items/2301.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2302.png b/WizBot/bin/Debug/data/lol/items/2302.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2302.png rename to WizBot/bin/Debug/data/lol/items/2302.png diff --git a/NadekoBot/bin/Debug/data/lol/items/2303.png b/WizBot/bin/Debug/data/lol/items/2303.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/2303.png rename to WizBot/bin/Debug/data/lol/items/2303.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3001.png b/WizBot/bin/Debug/data/lol/items/3001.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3001.png rename to WizBot/bin/Debug/data/lol/items/3001.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3003.png b/WizBot/bin/Debug/data/lol/items/3003.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3003.png rename to WizBot/bin/Debug/data/lol/items/3003.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3004.png b/WizBot/bin/Debug/data/lol/items/3004.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3004.png rename to WizBot/bin/Debug/data/lol/items/3004.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3006.png b/WizBot/bin/Debug/data/lol/items/3006.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3006.png rename to WizBot/bin/Debug/data/lol/items/3006.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3007.png b/WizBot/bin/Debug/data/lol/items/3007.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3007.png rename to WizBot/bin/Debug/data/lol/items/3007.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3008.png b/WizBot/bin/Debug/data/lol/items/3008.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3008.png rename to WizBot/bin/Debug/data/lol/items/3008.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3009.png b/WizBot/bin/Debug/data/lol/items/3009.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3009.png rename to WizBot/bin/Debug/data/lol/items/3009.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3010.png b/WizBot/bin/Debug/data/lol/items/3010.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3010.png rename to WizBot/bin/Debug/data/lol/items/3010.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3020.png b/WizBot/bin/Debug/data/lol/items/3020.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3020.png rename to WizBot/bin/Debug/data/lol/items/3020.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3022.png b/WizBot/bin/Debug/data/lol/items/3022.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3022.png rename to WizBot/bin/Debug/data/lol/items/3022.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3024.png b/WizBot/bin/Debug/data/lol/items/3024.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3024.png rename to WizBot/bin/Debug/data/lol/items/3024.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3025.png b/WizBot/bin/Debug/data/lol/items/3025.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3025.png rename to WizBot/bin/Debug/data/lol/items/3025.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3026.png b/WizBot/bin/Debug/data/lol/items/3026.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3026.png rename to WizBot/bin/Debug/data/lol/items/3026.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3027.png b/WizBot/bin/Debug/data/lol/items/3027.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3027.png rename to WizBot/bin/Debug/data/lol/items/3027.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3028.png b/WizBot/bin/Debug/data/lol/items/3028.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3028.png rename to WizBot/bin/Debug/data/lol/items/3028.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3029.png b/WizBot/bin/Debug/data/lol/items/3029.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3029.png rename to WizBot/bin/Debug/data/lol/items/3029.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3031.png b/WizBot/bin/Debug/data/lol/items/3031.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3031.png rename to WizBot/bin/Debug/data/lol/items/3031.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3033.png b/WizBot/bin/Debug/data/lol/items/3033.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3033.png rename to WizBot/bin/Debug/data/lol/items/3033.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3034.png b/WizBot/bin/Debug/data/lol/items/3034.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3034.png rename to WizBot/bin/Debug/data/lol/items/3034.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3035.png b/WizBot/bin/Debug/data/lol/items/3035.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3035.png rename to WizBot/bin/Debug/data/lol/items/3035.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3036.png b/WizBot/bin/Debug/data/lol/items/3036.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3036.png rename to WizBot/bin/Debug/data/lol/items/3036.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3040.png b/WizBot/bin/Debug/data/lol/items/3040.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3040.png rename to WizBot/bin/Debug/data/lol/items/3040.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3041.png b/WizBot/bin/Debug/data/lol/items/3041.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3041.png rename to WizBot/bin/Debug/data/lol/items/3041.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3042.png b/WizBot/bin/Debug/data/lol/items/3042.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3042.png rename to WizBot/bin/Debug/data/lol/items/3042.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3043.png b/WizBot/bin/Debug/data/lol/items/3043.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3043.png rename to WizBot/bin/Debug/data/lol/items/3043.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3044.png b/WizBot/bin/Debug/data/lol/items/3044.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3044.png rename to WizBot/bin/Debug/data/lol/items/3044.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3046.png b/WizBot/bin/Debug/data/lol/items/3046.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3046.png rename to WizBot/bin/Debug/data/lol/items/3046.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3047.png b/WizBot/bin/Debug/data/lol/items/3047.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3047.png rename to WizBot/bin/Debug/data/lol/items/3047.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3048.png b/WizBot/bin/Debug/data/lol/items/3048.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3048.png rename to WizBot/bin/Debug/data/lol/items/3048.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3050.png b/WizBot/bin/Debug/data/lol/items/3050.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3050.png rename to WizBot/bin/Debug/data/lol/items/3050.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3052.png b/WizBot/bin/Debug/data/lol/items/3052.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3052.png rename to WizBot/bin/Debug/data/lol/items/3052.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3053.png b/WizBot/bin/Debug/data/lol/items/3053.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3053.png rename to WizBot/bin/Debug/data/lol/items/3053.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3056.png b/WizBot/bin/Debug/data/lol/items/3056.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3056.png rename to WizBot/bin/Debug/data/lol/items/3056.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3057.png b/WizBot/bin/Debug/data/lol/items/3057.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3057.png rename to WizBot/bin/Debug/data/lol/items/3057.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3060.png b/WizBot/bin/Debug/data/lol/items/3060.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3060.png rename to WizBot/bin/Debug/data/lol/items/3060.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3065.png b/WizBot/bin/Debug/data/lol/items/3065.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3065.png rename to WizBot/bin/Debug/data/lol/items/3065.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3067.png b/WizBot/bin/Debug/data/lol/items/3067.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3067.png rename to WizBot/bin/Debug/data/lol/items/3067.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3068.png b/WizBot/bin/Debug/data/lol/items/3068.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3068.png rename to WizBot/bin/Debug/data/lol/items/3068.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3069.png b/WizBot/bin/Debug/data/lol/items/3069.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3069.png rename to WizBot/bin/Debug/data/lol/items/3069.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3070.png b/WizBot/bin/Debug/data/lol/items/3070.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3070.png rename to WizBot/bin/Debug/data/lol/items/3070.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3071.png b/WizBot/bin/Debug/data/lol/items/3071.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3071.png rename to WizBot/bin/Debug/data/lol/items/3071.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3072.png b/WizBot/bin/Debug/data/lol/items/3072.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3072.png rename to WizBot/bin/Debug/data/lol/items/3072.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3073.png b/WizBot/bin/Debug/data/lol/items/3073.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3073.png rename to WizBot/bin/Debug/data/lol/items/3073.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3074.png b/WizBot/bin/Debug/data/lol/items/3074.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3074.png rename to WizBot/bin/Debug/data/lol/items/3074.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3075.png b/WizBot/bin/Debug/data/lol/items/3075.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3075.png rename to WizBot/bin/Debug/data/lol/items/3075.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3077.png b/WizBot/bin/Debug/data/lol/items/3077.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3077.png rename to WizBot/bin/Debug/data/lol/items/3077.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3078.png b/WizBot/bin/Debug/data/lol/items/3078.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3078.png rename to WizBot/bin/Debug/data/lol/items/3078.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3082.png b/WizBot/bin/Debug/data/lol/items/3082.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3082.png rename to WizBot/bin/Debug/data/lol/items/3082.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3083.png b/WizBot/bin/Debug/data/lol/items/3083.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3083.png rename to WizBot/bin/Debug/data/lol/items/3083.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3084.png b/WizBot/bin/Debug/data/lol/items/3084.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3084.png rename to WizBot/bin/Debug/data/lol/items/3084.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3085.png b/WizBot/bin/Debug/data/lol/items/3085.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3085.png rename to WizBot/bin/Debug/data/lol/items/3085.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3086.png b/WizBot/bin/Debug/data/lol/items/3086.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3086.png rename to WizBot/bin/Debug/data/lol/items/3086.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3087.png b/WizBot/bin/Debug/data/lol/items/3087.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3087.png rename to WizBot/bin/Debug/data/lol/items/3087.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3089.png b/WizBot/bin/Debug/data/lol/items/3089.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3089.png rename to WizBot/bin/Debug/data/lol/items/3089.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3090.png b/WizBot/bin/Debug/data/lol/items/3090.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3090.png rename to WizBot/bin/Debug/data/lol/items/3090.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3091.png b/WizBot/bin/Debug/data/lol/items/3091.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3091.png rename to WizBot/bin/Debug/data/lol/items/3091.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3092.png b/WizBot/bin/Debug/data/lol/items/3092.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3092.png rename to WizBot/bin/Debug/data/lol/items/3092.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3094.png b/WizBot/bin/Debug/data/lol/items/3094.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3094.png rename to WizBot/bin/Debug/data/lol/items/3094.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3096.png b/WizBot/bin/Debug/data/lol/items/3096.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3096.png rename to WizBot/bin/Debug/data/lol/items/3096.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3097.png b/WizBot/bin/Debug/data/lol/items/3097.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3097.png rename to WizBot/bin/Debug/data/lol/items/3097.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3098.png b/WizBot/bin/Debug/data/lol/items/3098.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3098.png rename to WizBot/bin/Debug/data/lol/items/3098.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3100.png b/WizBot/bin/Debug/data/lol/items/3100.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3100.png rename to WizBot/bin/Debug/data/lol/items/3100.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3101.png b/WizBot/bin/Debug/data/lol/items/3101.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3101.png rename to WizBot/bin/Debug/data/lol/items/3101.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3102.png b/WizBot/bin/Debug/data/lol/items/3102.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3102.png rename to WizBot/bin/Debug/data/lol/items/3102.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3104.png b/WizBot/bin/Debug/data/lol/items/3104.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3104.png rename to WizBot/bin/Debug/data/lol/items/3104.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3105.png b/WizBot/bin/Debug/data/lol/items/3105.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3105.png rename to WizBot/bin/Debug/data/lol/items/3105.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3108.png b/WizBot/bin/Debug/data/lol/items/3108.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3108.png rename to WizBot/bin/Debug/data/lol/items/3108.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3110.png b/WizBot/bin/Debug/data/lol/items/3110.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3110.png rename to WizBot/bin/Debug/data/lol/items/3110.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3111.png b/WizBot/bin/Debug/data/lol/items/3111.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3111.png rename to WizBot/bin/Debug/data/lol/items/3111.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3112.png b/WizBot/bin/Debug/data/lol/items/3112.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3112.png rename to WizBot/bin/Debug/data/lol/items/3112.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3113.png b/WizBot/bin/Debug/data/lol/items/3113.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3113.png rename to WizBot/bin/Debug/data/lol/items/3113.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3114.png b/WizBot/bin/Debug/data/lol/items/3114.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3114.png rename to WizBot/bin/Debug/data/lol/items/3114.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3115.png b/WizBot/bin/Debug/data/lol/items/3115.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3115.png rename to WizBot/bin/Debug/data/lol/items/3115.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3116.png b/WizBot/bin/Debug/data/lol/items/3116.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3116.png rename to WizBot/bin/Debug/data/lol/items/3116.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3117.png b/WizBot/bin/Debug/data/lol/items/3117.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3117.png rename to WizBot/bin/Debug/data/lol/items/3117.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3122.png b/WizBot/bin/Debug/data/lol/items/3122.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3122.png rename to WizBot/bin/Debug/data/lol/items/3122.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3123.png b/WizBot/bin/Debug/data/lol/items/3123.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3123.png rename to WizBot/bin/Debug/data/lol/items/3123.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3124.png b/WizBot/bin/Debug/data/lol/items/3124.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3124.png rename to WizBot/bin/Debug/data/lol/items/3124.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3133.png b/WizBot/bin/Debug/data/lol/items/3133.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3133.png rename to WizBot/bin/Debug/data/lol/items/3133.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3134.png b/WizBot/bin/Debug/data/lol/items/3134.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3134.png rename to WizBot/bin/Debug/data/lol/items/3134.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3135.png b/WizBot/bin/Debug/data/lol/items/3135.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3135.png rename to WizBot/bin/Debug/data/lol/items/3135.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3136.png b/WizBot/bin/Debug/data/lol/items/3136.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3136.png rename to WizBot/bin/Debug/data/lol/items/3136.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3137.png b/WizBot/bin/Debug/data/lol/items/3137.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3137.png rename to WizBot/bin/Debug/data/lol/items/3137.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3139.png b/WizBot/bin/Debug/data/lol/items/3139.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3139.png rename to WizBot/bin/Debug/data/lol/items/3139.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3140.png b/WizBot/bin/Debug/data/lol/items/3140.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3140.png rename to WizBot/bin/Debug/data/lol/items/3140.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3142.png b/WizBot/bin/Debug/data/lol/items/3142.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3142.png rename to WizBot/bin/Debug/data/lol/items/3142.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3143.png b/WizBot/bin/Debug/data/lol/items/3143.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3143.png rename to WizBot/bin/Debug/data/lol/items/3143.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3144.png b/WizBot/bin/Debug/data/lol/items/3144.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3144.png rename to WizBot/bin/Debug/data/lol/items/3144.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3145.png b/WizBot/bin/Debug/data/lol/items/3145.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3145.png rename to WizBot/bin/Debug/data/lol/items/3145.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3146.png b/WizBot/bin/Debug/data/lol/items/3146.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3146.png rename to WizBot/bin/Debug/data/lol/items/3146.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3147.png b/WizBot/bin/Debug/data/lol/items/3147.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3147.png rename to WizBot/bin/Debug/data/lol/items/3147.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3151.png b/WizBot/bin/Debug/data/lol/items/3151.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3151.png rename to WizBot/bin/Debug/data/lol/items/3151.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3152.png b/WizBot/bin/Debug/data/lol/items/3152.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3152.png rename to WizBot/bin/Debug/data/lol/items/3152.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3153.png b/WizBot/bin/Debug/data/lol/items/3153.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3153.png rename to WizBot/bin/Debug/data/lol/items/3153.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3154.png b/WizBot/bin/Debug/data/lol/items/3154.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3154.png rename to WizBot/bin/Debug/data/lol/items/3154.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3155.png b/WizBot/bin/Debug/data/lol/items/3155.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3155.png rename to WizBot/bin/Debug/data/lol/items/3155.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3156.png b/WizBot/bin/Debug/data/lol/items/3156.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3156.png rename to WizBot/bin/Debug/data/lol/items/3156.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3157.png b/WizBot/bin/Debug/data/lol/items/3157.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3157.png rename to WizBot/bin/Debug/data/lol/items/3157.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3158.png b/WizBot/bin/Debug/data/lol/items/3158.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3158.png rename to WizBot/bin/Debug/data/lol/items/3158.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3165.png b/WizBot/bin/Debug/data/lol/items/3165.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3165.png rename to WizBot/bin/Debug/data/lol/items/3165.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3170.png b/WizBot/bin/Debug/data/lol/items/3170.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3170.png rename to WizBot/bin/Debug/data/lol/items/3170.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3174.png b/WizBot/bin/Debug/data/lol/items/3174.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3174.png rename to WizBot/bin/Debug/data/lol/items/3174.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3175.png b/WizBot/bin/Debug/data/lol/items/3175.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3175.png rename to WizBot/bin/Debug/data/lol/items/3175.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3180.png b/WizBot/bin/Debug/data/lol/items/3180.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3180.png rename to WizBot/bin/Debug/data/lol/items/3180.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3181.png b/WizBot/bin/Debug/data/lol/items/3181.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3181.png rename to WizBot/bin/Debug/data/lol/items/3181.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3184.png b/WizBot/bin/Debug/data/lol/items/3184.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3184.png rename to WizBot/bin/Debug/data/lol/items/3184.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3185.png b/WizBot/bin/Debug/data/lol/items/3185.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3185.png rename to WizBot/bin/Debug/data/lol/items/3185.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3187.png b/WizBot/bin/Debug/data/lol/items/3187.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3187.png rename to WizBot/bin/Debug/data/lol/items/3187.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3190.png b/WizBot/bin/Debug/data/lol/items/3190.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3190.png rename to WizBot/bin/Debug/data/lol/items/3190.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3191.png b/WizBot/bin/Debug/data/lol/items/3191.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3191.png rename to WizBot/bin/Debug/data/lol/items/3191.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3196.png b/WizBot/bin/Debug/data/lol/items/3196.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3196.png rename to WizBot/bin/Debug/data/lol/items/3196.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3197.png b/WizBot/bin/Debug/data/lol/items/3197.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3197.png rename to WizBot/bin/Debug/data/lol/items/3197.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3198.png b/WizBot/bin/Debug/data/lol/items/3198.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3198.png rename to WizBot/bin/Debug/data/lol/items/3198.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3200.png b/WizBot/bin/Debug/data/lol/items/3200.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3200.png rename to WizBot/bin/Debug/data/lol/items/3200.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3211.png b/WizBot/bin/Debug/data/lol/items/3211.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3211.png rename to WizBot/bin/Debug/data/lol/items/3211.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3222.png b/WizBot/bin/Debug/data/lol/items/3222.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3222.png rename to WizBot/bin/Debug/data/lol/items/3222.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3240.png b/WizBot/bin/Debug/data/lol/items/3240.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3240.png rename to WizBot/bin/Debug/data/lol/items/3240.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3241.png b/WizBot/bin/Debug/data/lol/items/3241.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3241.png rename to WizBot/bin/Debug/data/lol/items/3241.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3242.png b/WizBot/bin/Debug/data/lol/items/3242.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3242.png rename to WizBot/bin/Debug/data/lol/items/3242.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3243.png b/WizBot/bin/Debug/data/lol/items/3243.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3243.png rename to WizBot/bin/Debug/data/lol/items/3243.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3285.png b/WizBot/bin/Debug/data/lol/items/3285.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3285.png rename to WizBot/bin/Debug/data/lol/items/3285.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3301.png b/WizBot/bin/Debug/data/lol/items/3301.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3301.png rename to WizBot/bin/Debug/data/lol/items/3301.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3302.png b/WizBot/bin/Debug/data/lol/items/3302.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3302.png rename to WizBot/bin/Debug/data/lol/items/3302.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3303.png b/WizBot/bin/Debug/data/lol/items/3303.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3303.png rename to WizBot/bin/Debug/data/lol/items/3303.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3340.png b/WizBot/bin/Debug/data/lol/items/3340.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3340.png rename to WizBot/bin/Debug/data/lol/items/3340.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3341.png b/WizBot/bin/Debug/data/lol/items/3341.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3341.png rename to WizBot/bin/Debug/data/lol/items/3341.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3345.png b/WizBot/bin/Debug/data/lol/items/3345.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3345.png rename to WizBot/bin/Debug/data/lol/items/3345.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3348.png b/WizBot/bin/Debug/data/lol/items/3348.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3348.png rename to WizBot/bin/Debug/data/lol/items/3348.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3361.png b/WizBot/bin/Debug/data/lol/items/3361.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3361.png rename to WizBot/bin/Debug/data/lol/items/3361.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3362.png b/WizBot/bin/Debug/data/lol/items/3362.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3362.png rename to WizBot/bin/Debug/data/lol/items/3362.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3363.png b/WizBot/bin/Debug/data/lol/items/3363.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3363.png rename to WizBot/bin/Debug/data/lol/items/3363.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3364.png b/WizBot/bin/Debug/data/lol/items/3364.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3364.png rename to WizBot/bin/Debug/data/lol/items/3364.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3401.png b/WizBot/bin/Debug/data/lol/items/3401.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3401.png rename to WizBot/bin/Debug/data/lol/items/3401.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3410.png b/WizBot/bin/Debug/data/lol/items/3410.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3410.png rename to WizBot/bin/Debug/data/lol/items/3410.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3416.png b/WizBot/bin/Debug/data/lol/items/3416.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3416.png rename to WizBot/bin/Debug/data/lol/items/3416.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3422.png b/WizBot/bin/Debug/data/lol/items/3422.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3422.png rename to WizBot/bin/Debug/data/lol/items/3422.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3455.png b/WizBot/bin/Debug/data/lol/items/3455.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3455.png rename to WizBot/bin/Debug/data/lol/items/3455.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3460.png b/WizBot/bin/Debug/data/lol/items/3460.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3460.png rename to WizBot/bin/Debug/data/lol/items/3460.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3461.png b/WizBot/bin/Debug/data/lol/items/3461.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3461.png rename to WizBot/bin/Debug/data/lol/items/3461.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3504.png b/WizBot/bin/Debug/data/lol/items/3504.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3504.png rename to WizBot/bin/Debug/data/lol/items/3504.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3508.png b/WizBot/bin/Debug/data/lol/items/3508.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3508.png rename to WizBot/bin/Debug/data/lol/items/3508.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3512.png b/WizBot/bin/Debug/data/lol/items/3512.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3512.png rename to WizBot/bin/Debug/data/lol/items/3512.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3599.png b/WizBot/bin/Debug/data/lol/items/3599.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3599.png rename to WizBot/bin/Debug/data/lol/items/3599.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3671.png b/WizBot/bin/Debug/data/lol/items/3671.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3671.png rename to WizBot/bin/Debug/data/lol/items/3671.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3672.png b/WizBot/bin/Debug/data/lol/items/3672.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3672.png rename to WizBot/bin/Debug/data/lol/items/3672.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3673.png b/WizBot/bin/Debug/data/lol/items/3673.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3673.png rename to WizBot/bin/Debug/data/lol/items/3673.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3674.png b/WizBot/bin/Debug/data/lol/items/3674.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3674.png rename to WizBot/bin/Debug/data/lol/items/3674.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3706.png b/WizBot/bin/Debug/data/lol/items/3706.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3706.png rename to WizBot/bin/Debug/data/lol/items/3706.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3711.png b/WizBot/bin/Debug/data/lol/items/3711.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3711.png rename to WizBot/bin/Debug/data/lol/items/3711.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3715.png b/WizBot/bin/Debug/data/lol/items/3715.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3715.png rename to WizBot/bin/Debug/data/lol/items/3715.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3742.png b/WizBot/bin/Debug/data/lol/items/3742.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3742.png rename to WizBot/bin/Debug/data/lol/items/3742.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3748.png b/WizBot/bin/Debug/data/lol/items/3748.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3748.png rename to WizBot/bin/Debug/data/lol/items/3748.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3751.png b/WizBot/bin/Debug/data/lol/items/3751.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3751.png rename to WizBot/bin/Debug/data/lol/items/3751.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3800.png b/WizBot/bin/Debug/data/lol/items/3800.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3800.png rename to WizBot/bin/Debug/data/lol/items/3800.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3801.png b/WizBot/bin/Debug/data/lol/items/3801.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3801.png rename to WizBot/bin/Debug/data/lol/items/3801.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3812.png b/WizBot/bin/Debug/data/lol/items/3812.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3812.png rename to WizBot/bin/Debug/data/lol/items/3812.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3901.png b/WizBot/bin/Debug/data/lol/items/3901.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3901.png rename to WizBot/bin/Debug/data/lol/items/3901.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3902.png b/WizBot/bin/Debug/data/lol/items/3902.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3902.png rename to WizBot/bin/Debug/data/lol/items/3902.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3903.png b/WizBot/bin/Debug/data/lol/items/3903.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3903.png rename to WizBot/bin/Debug/data/lol/items/3903.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3930.png b/WizBot/bin/Debug/data/lol/items/3930.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3930.png rename to WizBot/bin/Debug/data/lol/items/3930.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3931.png b/WizBot/bin/Debug/data/lol/items/3931.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3931.png rename to WizBot/bin/Debug/data/lol/items/3931.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3932.png b/WizBot/bin/Debug/data/lol/items/3932.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3932.png rename to WizBot/bin/Debug/data/lol/items/3932.png diff --git a/NadekoBot/bin/Debug/data/lol/items/3933.png b/WizBot/bin/Debug/data/lol/items/3933.png similarity index 100% rename from NadekoBot/bin/Debug/data/lol/items/3933.png rename to WizBot/bin/Debug/data/lol/items/3933.png diff --git a/NadekoBot/bin/Debug/data/magicitems.json b/WizBot/bin/Debug/data/magicitems.json similarity index 100% rename from NadekoBot/bin/Debug/data/magicitems.json rename to WizBot/bin/Debug/data/magicitems.json diff --git a/WizBot/bin/Debug/data/permissions/128191554350219264.json b/WizBot/bin/Debug/data/permissions/128191554350219264.json new file mode 100644 index 000000000..d4b3b5ab0 --- /dev/null +++ b/WizBot/bin/Debug/data/permissions/128191554350219264.json @@ -0,0 +1,28 @@ +{ + "PermissionsControllerRole": "WizBot", + "Verbose": true, + "Id": 128191554350219264, + "Permissions": { + "Name": "Neko Kingdom", + "Modules": { + "NSFW": false + }, + "Commands": {}, + "FilterInvites": false, + "FilterWords": false + }, + "Words": [], + "UserPermissions": { + "126918396854927360": { + "Name": "Mizore-Chan", + "Modules": { + "Permissions": false + }, + "Commands": {}, + "FilterInvites": false, + "FilterWords": false + } + }, + "ChannelPermissions": {}, + "RolePermissions": {} +} \ No newline at end of file diff --git a/WizBot/bin/Debug/data/permissions/99273784988557312.json b/WizBot/bin/Debug/data/permissions/99273784988557312.json new file mode 100644 index 000000000..24ec6f5bb --- /dev/null +++ b/WizBot/bin/Debug/data/permissions/99273784988557312.json @@ -0,0 +1,18 @@ +{ + "PermissionsControllerRole": "Wiz Bot", + "Verbose": true, + "Id": 99273784988557312, + "Permissions": { + "Name": "Wizkiller96 Network", + "Modules": { + "NSFW": false + }, + "Commands": {}, + "FilterInvites": false, + "FilterWords": false + }, + "Words": [], + "UserPermissions": {}, + "ChannelPermissions": {}, + "RolePermissions": {} +} \ No newline at end of file diff --git a/WizBot/bin/Debug/data/pokemon/LICENSE.txt b/WizBot/bin/Debug/data/pokemon/LICENSE.txt new file mode 100644 index 000000000..283bf7a13 --- /dev/null +++ b/WizBot/bin/Debug/data/pokemon/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) 2011-2016 Guangcong Luo and other contributors +http://pokemonshowdown.com/ + +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. diff --git a/WizBot/bin/Debug/data/pokemon/pokemon_abilities.json b/WizBot/bin/Debug/data/pokemon/pokemon_abilities.json new file mode 100644 index 000000000..7cfde85f9 --- /dev/null +++ b/WizBot/bin/Debug/data/pokemon/pokemon_abilities.json @@ -0,0 +1,1596 @@ +{ + "adaptability": { + "desc": "This Pokemon's moves that match one of its types have a same-type attack bonus (STAB) of 2 instead of 1.5.", + "shortDesc": "This Pokemon's same-type attack bonus (STAB) is 2 instead of 1.5.", + "id": "adaptability", + "name": "Adaptability", + "rating": 3.5, + "num": 91 + }, + "aftermath": { + "desc": "If this Pokemon is knocked out with a contact move, that move's user loses 1/4 of its maximum HP, rounded down. If any active Pokemon has the Ability Damp, this effect is prevented.", + "shortDesc": "If this Pokemon is KOed with a contact move, that move's user loses 1/4 its max HP.", + "id": "aftermath", + "name": "Aftermath", + "onAfterDamageOrder": 1, + "rating": 2.5, + "num": 106 + }, + "aerilate": { + "desc": "This Pokemon's Normal-type moves become Flying-type moves and have their power multiplied by 1.3. This effect comes after other effects that change a move's type, but before Ion Deluge and Electrify's effects.", + "shortDesc": "This Pokemon's Normal-type moves become Flying type and have 1.3x power.", + "onModifyMovePriority": -1, + "effect": { + "duration": 1, + "onBasePowerPriority": 8 + }, + "id": "aerilate", + "name": "Aerilate", + "rating": 4, + "num": 185 + }, + "airlock": { + "shortDesc": "While this Pokemon is active, the effects of weather conditions are disabled.", + "suppressWeather": true, + "id": "airlock", + "name": "Air Lock", + "rating": 3, + "num": 76 + }, + "analytic": { + "desc": "The power of this Pokemon's move is multiplied by 1.3 if it is the last to move in a turn. Does not affect Doom Desire and Future Sight.", + "shortDesc": "This Pokemon's attacks have 1.3x power if it is the last to move in a turn.", + "onBasePowerPriority": 8, + "id": "analytic", + "name": "Analytic", + "rating": 2, + "num": 148 + }, + "angerpoint": { + "desc": "If this Pokemon, but not its substitute, is struck by a critical hit, its Attack is raised by 12 stages.", + "shortDesc": "If this Pokemon (not its substitute) takes a critical hit, its Attack is raised 12 stages.", + "id": "angerpoint", + "name": "Anger Point", + "rating": 2, + "num": 83 + }, + "anticipation": { + "desc": "On switch-in, this Pokemon is alerted if any opposing Pokemon has an attack that is super effective on this Pokemon, or an OHKO move. Counter, Metal Burst, and Mirror Coat count as attacking moves of their respective types, while Hidden Power, Judgment, Natural Gift, Techno Blast, and Weather Ball are considered Normal-type moves.", + "shortDesc": "On switch-in, this Pokemon shudders if any foe has a supereffective or OHKO move.", + "id": "anticipation", + "name": "Anticipation", + "rating": 1, + "num": 107 + }, + "arenatrap": { + "desc": "Prevents adjacent opposing Pokemon from choosing to switch out unless they are immune to trapping or are airborne.", + "shortDesc": "Prevents adjacent foes from choosing to switch unless they are airborne.", + "id": "arenatrap", + "name": "Arena Trap", + "rating": 4.5, + "num": 71 + }, + "aromaveil": { + "desc": "This Pokemon and its allies cannot be affected by Attract, Disable, Encore, Heal Block, Taunt, or Torment.", + "shortDesc": "Protects user/allies from Attract, Disable, Encore, Heal Block, Taunt, and Torment.", + "id": "aromaveil", + "name": "Aroma Veil", + "rating": 1.5, + "num": 165 + }, + "aurabreak": { + "desc": "While this Pokemon is active, the effects of the Abilities Dark Aura and Fairy Aura are reversed, multiplying the power of Dark- and Fairy-type moves, respectively, by 3/4 instead of 1.33.", + "shortDesc": "While this Pokemon is active, the Dark Aura and Fairy Aura power modifier is 0.75x.", + "effect": { + "duration": 1 + }, + "id": "aurabreak", + "name": "Aura Break", + "rating": 2, + "num": 188 + }, + "baddreams": { + "desc": "Causes adjacent opposing Pokemon to lose 1/8 of their maximum HP, rounded down, at the end of each turn if they are asleep.", + "shortDesc": "Causes sleeping adjacent foes to lose 1/8 of their max HP at the end of each turn.", + "onResidualOrder": 26, + "onResidualSubOrder": 1, + "id": "baddreams", + "name": "Bad Dreams", + "rating": 2, + "num": 123 + }, + "battlearmor": { + "shortDesc": "This Pokemon cannot be struck by a critical hit.", + "onCriticalHit": false, + "id": "battlearmor", + "name": "Battle Armor", + "rating": 1, + "num": 4 + }, + "bigpecks": { + "shortDesc": "Prevents other Pokemon from lowering this Pokemon's Defense stat stage.", + "id": "bigpecks", + "name": "Big Pecks", + "rating": 0.5, + "num": 145 + }, + "blaze": { + "desc": "When this Pokemon has 1/3 or less of its maximum HP, rounded down, its attacking stat is multiplied by 1.5 while using a Fire-type attack.", + "shortDesc": "When this Pokemon has 1/3 or less of its max HP, its Fire attacks do 1.5x damage.", + "onModifyAtkPriority": 5, + "onModifySpAPriority": 5, + "id": "blaze", + "name": "Blaze", + "rating": 2, + "num": 66 + }, + "bulletproof": { + "desc": "This Pokemon is immune to ballistic moves. Ballistic moves include BulvarSeed, Octazooka, Barrage, Rock Wrecker, Zap Cannon, Acid Spray, Aura Sphere, Focus Blast, and all moves with Ball or Bomb in their name.", + "shortDesc": "Makes user immune to ballistic moves (Shadow Ball, Sludge Bomb, Focus Blast, etc).", + "id": "bulletproof", + "name": "Bulletproof", + "rating": 3, + "num": 171 + }, + "cheekpouch": { + "desc": "If this Pokemon eats a Berry, it restores 1/3 of its maximum HP, rounded down, in addition to the Berry's effect.", + "shortDesc": "If this Pokemon eats a Berry, it restores 1/3 of its max HP after the Berry's effect.", + "id": "cheekpouch", + "name": "Cheek Pouch", + "rating": 2, + "num": 167 + }, + "chlorophyll": { + "shortDesc": "If Sunny Day is active, this Pokemon's Speed is doubled.", + "id": "chlorophyll", + "name": "Chlorophyll", + "rating": 2.5, + "num": 34 + }, + "clearbody": { + "shortDesc": "Prevents other Pokemon from lowering this Pokemon's stat stages.", + "id": "clearbody", + "name": "Clear Body", + "rating": 2, + "num": 29 + }, + "cloudnine": { + "shortDesc": "While this Pokemon is active, the effects of weather conditions are disabled.", + "suppressWeather": true, + "id": "cloudnine", + "name": "Cloud Nine", + "rating": 3, + "num": 13 + }, + "colorchange": { + "desc": "This Pokemon's type changes to match the type of the last move that hit it, unless that type is already one of its types. This effect applies after all hits from a multi-hit move; Sheer Force prevents it from activating if the move has a secondary effect.", + "shortDesc": "This Pokemon's type changes to the type of a move it's hit by, unless it has the type.", + "id": "colorchange", + "name": "Color Change", + "rating": 1, + "num": 16 + }, + "competitive": { + "desc": "This Pokemon's Special Attack is raised by 2 stages for each of its stat stages that is lowered by an opposing Pokemon.", + "shortDesc": "This Pokemon's Sp. Atk is raised by 2 for each of its stats that is lowered by a foe.", + "id": "competitive", + "name": "Competitive", + "rating": 2.5, + "num": 172 + }, + "compoundeyes": { + "shortDesc": "This Pokemon's moves have their accuracy multiplied by 1.3.", + "id": "compoundeyes", + "name": "Compound Eyes", + "rating": 3.5, + "num": 14 + }, + "contrary": { + "shortDesc": "If this Pokemon has a stat stage raised it is lowered instead, and vice versa.", + "id": "contrary", + "name": "Contrary", + "rating": 4, + "num": 126 + }, + "cursedbody": { + "desc": "If this Pokemon is hit by an attack, there is a 30% chance that move gets disabled unless one of the attacker's moves is already disabled.", + "shortDesc": "If this Pokemon is hit by an attack, there is a 30% chance that move gets disabled.", + "id": "cursedbody", + "name": "Cursed Body", + "rating": 2, + "num": 130 + }, + "cutecharm": { + "desc": "There is a 30% chance a Pokemon making contact with this Pokemon will become infatuated if it is of the opposite gender.", + "shortDesc": "30% chance of infatuating Pokemon of the opposite gender if they make contact.", + "id": "cutecharm", + "name": "Cute Charm", + "rating": 1, + "num": 56 + }, + "damp": { + "desc": "While this Pokemon is active, Self-Destruct, Explosion, and the Ability Aftermath are prevented from having an effect.", + "shortDesc": "While this Pokemon is active, Self-Destruct, Explosion, and Aftermath have no effect.", + "id": "damp", + "name": "Damp", + "rating": 1, + "num": 6 + }, + "darkaura": { + "desc": "While this Pokemon is active, the power of Dark-type moves used by active Pokemon is multiplied by 1.33.", + "shortDesc": "While this Pokemon is active, a Dark move used by any Pokemon has 1.33x power.", + "id": "darkaura", + "name": "Dark Aura", + "rating": 3, + "num": 186 + }, + "defeatist": { + "desc": "While this Pokemon has 1/2 or less of its maximum HP, its Attack and Special Attack are halved.", + "shortDesc": "While this Pokemon has 1/2 or less of its max HP, its Attack and Sp. Atk are halved.", + "onModifyAtkPriority": 5, + "onModifySpAPriority": 5, + "id": "defeatist", + "name": "Defeatist", + "rating": -1, + "num": 129 + }, + "defiant": { + "desc": "This Pokemon's Attack is raised by 2 stages for each of its stat stages that is lowered by an opposing Pokemon.", + "shortDesc": "This Pokemon's Attack is raised by 2 for each of its stats that is lowered by a foe.", + "id": "defiant", + "name": "Defiant", + "rating": 2.5, + "num": 128 + }, + "deltastream": { + "desc": "On switch-in, the weather becomes strong winds that remove the weaknesses of the Flying type from Flying-type Pokemon. This weather remains in effect until this Ability is no longer active for any Pokemon, or the weather is changed by Desolate Land or Primordial Sea.", + "shortDesc": "On switch-in, strong winds begin until this Ability is not active in battle.", + "id": "deltastream", + "name": "Delta Stream", + "rating": 5, + "num": 191 + }, + "desolateland": { + "desc": "On switch-in, the weather becomes extremely harsh sunlight that prevents damaging Water-type moves from executing, in addition to all the effects of Sunny Day. This weather remains in effect until this Ability is no longer active for any Pokemon, or the weather is changed by Delta Stream or Primordial Sea.", + "shortDesc": "On switch-in, extremely harsh sunlight begins until this Ability is not active in battle.", + "id": "desolateland", + "name": "Desolate Land", + "rating": 5, + "num": 190 + }, + "download": { + "desc": "On switch-in, this Pokemon's Attack or Special Attack is raised by 1 stage based on the weaker combined defensive stat of all opposing Pokemon. Attack is raised if their Defense is lower, and Special Attack is raised if their Special Defense is the same or lower.", + "shortDesc": "On switch-in, Attack or Sp. Atk is raised 1 stage based on the foes' weaker Defense.", + "id": "download", + "name": "Download", + "rating": 4, + "num": 88 + }, + "drizzle": { + "shortDesc": "On switch-in, this Pokemon summons Rain Dance.", + "id": "drizzle", + "name": "Drizzle", + "rating": 4.5, + "num": 2 + }, + "drought": { + "shortDesc": "On switch-in, this Pokemon summons Sunny Day.", + "id": "drought", + "name": "Drought", + "rating": 4.5, + "num": 70 + }, + "dryskin": { + "desc": "This Pokemon is immune to Water-type moves and restores 1/4 of its maximum HP, rounded down, when hit by a Water-type move. The power of Fire-type moves is multiplied by 1.25 when used on this Pokemon. At the end of each turn, this Pokemon restores 1/8 of its maximum HP, rounded down, if the weather is Rain Dance, and loses 1/8 of its maximum HP, rounded down, if the weather is Sunny Day.", + "shortDesc": "This Pokemon is healed 1/4 by Water, 1/8 by Rain; is hurt 1.25x by Fire, 1/8 by Sun.", + "onBasePowerPriority": 7, + "id": "dryskin", + "name": "Dry Skin", + "rating": 3.5, + "num": 87 + }, + "earlybird": { + "shortDesc": "This Pokemon's sleep counter drops by 2 instead of 1.", + "id": "earlybird", + "name": "Early Bird", + "rating": 2.5, + "num": 48 + }, + "effectspore": { + "desc": "30% chance a Pokemon making contact with this Pokemon will be poisoned, paralyzed, or fall asleep.", + "shortDesc": "30% chance of poison/paralysis/sleep on others making contact with this Pokemon.", + "id": "effectspore", + "name": "Effect Spore", + "rating": 2, + "num": 27 + }, + "fairyaura": { + "desc": "While this Pokemon is active, the power of Fairy-type moves used by active Pokemon is multiplied by 1.33.", + "shortDesc": "While this Pokemon is active, a Fairy move used by any Pokemon has 1.33x power.", + "id": "fairyaura", + "name": "Fairy Aura", + "rating": 3, + "num": 187 + }, + "filter": { + "shortDesc": "This Pokemon receives 3/4 damage from supereffective attacks.", + "id": "filter", + "name": "Filter", + "rating": 3, + "num": 111 + }, + "flamebody": { + "shortDesc": "30% chance a Pokemon making contact with this Pokemon will be burned.", + "id": "flamebody", + "name": "Flame Body", + "rating": 2, + "num": 49 + }, + "flareboost": { + "desc": "While this Pokemon is burned, the power of its special attacks is multiplied by 1.5.", + "shortDesc": "While this Pokemon is burned, its special attacks have 1.5x power.", + "onBasePowerPriority": 8, + "id": "flareboost", + "name": "Flare Boost", + "rating": 2.5, + "num": 138 + }, + "flashfire": { + "desc": "This Pokemon is immune to Fire-type moves. The first time it is hit by a Fire-type move, its attacking stat is multiplied by 1.5 while using a Fire-type attack as long as it remains active and has this Ability. If this Pokemon is frozen, it cannot be defrosted by Fire-type attacks.", + "shortDesc": "This Pokemon's Fire attacks do 1.5x damage if hit by one Fire move; Fire immunity.", + "effect": { + "noCopy": true, + "onModifyAtkPriority": 5, + "onModifySpAPriority": 5 + }, + "id": "flashfire", + "name": "Flash Fire", + "rating": 3, + "num": 18 + }, + "flowergift": { + "desc": "If this Pokemon is a Cherrim and Sunny Day is active, it changes to Sunshine Form and the Attack and Special Defense of it and its allies are multiplied by 1.5.", + "shortDesc": "If user is Cherrim and Sunny Day is active, it and allies' Attack and Sp. Def are 1.5x.", + "onModifyAtkPriority": 3, + "onModifySpDPriority": 4, + "id": "flowergift", + "name": "Flower Gift", + "rating": 2.5, + "num": 122 + }, + "flowerveil": { + "desc": "Grass-type Pokemon on this Pokemon's side cannot have their stat stages lowered by other Pokemon or have a major status condition inflicted on them by other Pokemon.", + "shortDesc": "This side's Grass types can't have stats lowered or status inflicted by other Pokemon.", + "id": "flowerveil", + "name": "Flower Veil", + "rating": 0, + "num": 166 + }, + "forecast": { + "desc": "If this Pokemon is a Castform, its type changes to the current weather condition's type, except Sandstorm.", + "shortDesc": "Castform's type changes to the current weather condition's type, except Sandstorm.", + "id": "forecast", + "name": "Forecast", + "rating": 3, + "num": 59 + }, + "forewarn": { + "desc": "On switch-in, this Pokemon is alerted to the move with the highest power, at random, known by an opposing Pokemon.", + "shortDesc": "On switch-in, this Pokemon is alerted to the foes' move with the highest power.", + "id": "forewarn", + "name": "Forewarn", + "rating": 1, + "num": 108 + }, + "friendguard": { + "shortDesc": "This Pokemon's allies receive 3/4 damage from other Pokemon's attacks.", + "id": "friendguard", + "name": "Friend Guard", + "rating": 0, + "num": 132 + }, + "frisk": { + "shortDesc": "On switch-in, this Pokemon identifies the held items of all opposing Pokemon.", + "id": "frisk", + "name": "Frisk", + "rating": 1.5, + "num": 119 + }, + "furcoat": { + "shortDesc": "This Pokemon's Defense is doubled.", + "onModifyDefPriority": 6, + "id": "furcoat", + "name": "Fur Coat", + "rating": 3.5, + "num": 169 + }, + "galewings": { + "shortDesc": "This Pokemon's Flying-type moves have their priority increased by 1.", + "id": "galewings", + "name": "Gale Wings", + "rating": 4.5, + "num": 177 + }, + "gluttony": { + "shortDesc": "When this Pokemon has 1/2 or less of its maximum HP, it uses certain Berries early.", + "id": "gluttony", + "name": "Gluttony", + "rating": 1, + "num": 82 + }, + "gooey": { + "shortDesc": "Pokemon making contact with this Pokemon have their Speed lowered by 1 stage.", + "id": "gooey", + "name": "Gooey", + "rating": 2.5, + "num": 183 + }, + "grasspelt": { + "shortDesc": "If Grassy Terrain is active, this Pokemon's Defense is multiplied by 1.5.", + "onModifyDefPriority": 6, + "id": "grasspelt", + "name": "Grass Pelt", + "rating": 0.5, + "num": 179 + }, + "guts": { + "desc": "If this Pokemon has a major status condition, its Attack is multiplied by 1.5; burn's physical damage halving is ignored.", + "shortDesc": "If this Pokemon is statused, its Attack is 1.5x; ignores burn halving physical damage.", + "onModifyAtkPriority": 5, + "id": "guts", + "name": "Guts", + "rating": 3, + "num": 62 + }, + "harvest": { + "desc": "If the last item this Pokemon used is a Berry, there is a 50% chance it gets restored at the end of each turn. If Sunny Day is active, this chance is 100%.", + "shortDesc": "If last item used is a Berry, 50% chance to restore it each end of turn. 100% in Sun.", + "id": "harvest", + "name": "Harvest", + "onResidualOrder": 26, + "onResidualSubOrder": 1, + "rating": 2.5, + "num": 139 + }, + "healer": { + "desc": "There is a 30% chance of curing an adjacent ally's major status condition at the end of each turn.", + "shortDesc": "30% chance of curing an adjacent ally's status at the end of each turn.", + "id": "healer", + "name": "Healer", + "onResidualOrder": 5, + "onResidualSubOrder": 1, + "rating": 0, + "num": 131 + }, + "heatproof": { + "desc": "The power of Fire-type attacks against this Pokemon is halved, and any burn damage taken is 1/16 of its maximum HP, rounded down.", + "shortDesc": "The power of Fire-type attacks against this Pokemon is halved; burn damage halved.", + "onBasePowerPriority": 7, + "id": "heatproof", + "name": "Heatproof", + "rating": 2.5, + "num": 85 + }, + "heavymetal": { + "shortDesc": "This Pokemon's weight is doubled.", + "id": "heavymetal", + "name": "Heavy Metal", + "rating": -1, + "num": 134 + }, + "honeygather": { + "shortDesc": "No competitive use.", + "id": "honeygather", + "name": "Honey Gather", + "rating": 0, + "num": 118 + }, + "hugepower": { + "shortDesc": "This Pokemon's Attack is doubled.", + "onModifyAtkPriority": 5, + "id": "hugepower", + "name": "Huge Power", + "rating": 5, + "num": 37 + }, + "hustle": { + "desc": "This Pokemon's Attack is multiplied by 1.5 and the accuracy of its physical attacks is multiplied by 0.8.", + "shortDesc": "This Pokemon's Attack is 1.5x and accuracy of its physical attacks is 0.8x.", + "onModifyAtkPriority": 5, + "id": "hustle", + "name": "Hustle", + "rating": 3, + "num": 55 + }, + "hydration": { + "desc": "This Pokemon has its major status condition cured at the end of each turn if Rain Dance is active.", + "shortDesc": "This Pokemon has its status cured at the end of each turn if Rain Dance is active.", + "onResidualOrder": 5, + "onResidualSubOrder": 1, + "id": "hydration", + "name": "Hydration", + "rating": 2, + "num": 93 + }, + "hypercutter": { + "shortDesc": "Prevents other Pokemon from lowering this Pokemon's Attack stat stage.", + "id": "hypercutter", + "name": "Hyper Cutter", + "rating": 1.5, + "num": 52 + }, + "icebody": { + "desc": "If Hail is active, this Pokemon restores 1/16 of its maximum HP, rounded down, at the end of each turn. This Pokemon takes no damage from Hail.", + "shortDesc": "If Hail is active, this Pokemon heals 1/16 of its max HP each turn; immunity to Hail.", + "id": "icebody", + "name": "Ice Body", + "rating": 1.5, + "num": 115 + }, + "illuminate": { + "shortDesc": "No competitive use.", + "id": "illuminate", + "name": "Illuminate", + "rating": 0, + "num": 35 + }, + "illusion": { + "desc": "When this Pokemon switches in, it appears as the last unfainted Pokemon in its party until it takes direct damage from another Pokemon's attack. This Pokemon's actual level and HP are displayed instead of those of the mimicked Pokemon.", + "shortDesc": "This Pokemon appears as the last Pokemon in the party until it takes direct damage.", + "id": "illusion", + "name": "Illusion", + "rating": 4.5, + "num": 149 + }, + "immunity": { + "shortDesc": "This Pokemon cannot be poisoned. Gaining this Ability while poisoned cures it.", + "id": "immunity", + "name": "Immunity", + "rating": 2, + "num": 17 + }, + "imposter": { + "desc": "On switch-in, this Pokemon Transforms into the opposing Pokemon that is facing it. If there is no Pokemon at that position, this Pokemon does not Transform.", + "shortDesc": "On switch-in, this Pokemon Transforms into the opposing Pokemon that is facing it.", + "id": "imposter", + "name": "Imposter", + "rating": 4.5, + "num": 150 + }, + "infiltrator": { + "desc": "This Pokemon's moves ignore substitutes and the opposing side's Reflect, Light Screen, Safeguard, and Mist.", + "shortDesc": "Moves ignore substitutes and opposing Reflect, Light Screen, Safeguard, and Mist.", + "id": "infiltrator", + "name": "Infiltrator", + "rating": 3, + "num": 151 + }, + "innerfocus": { + "shortDesc": "This Pokemon cannot be made to flinch.", + "onFlinch": false, + "id": "innerfocus", + "name": "Inner Focus", + "rating": 1.5, + "num": 39 + }, + "insomnia": { + "shortDesc": "This Pokemon cannot fall asleep. Gaining this Ability while asleep cures it.", + "id": "insomnia", + "name": "Insomnia", + "rating": 2, + "num": 15 + }, + "intimidate": { + "desc": "On switch-in, this Pokemon lowers the Attack of adjacent opposing Pokemon by 1 stage. Pokemon behind a substitute are immune.", + "shortDesc": "On switch-in, this Pokemon lowers the Attack of adjacent opponents by 1 stage.", + "id": "intimidate", + "name": "Intimidate", + "rating": 3.5, + "num": 22 + }, + "ironbarbs": { + "desc": "Pokemon making contact with this Pokemon lose 1/8 of their maximum HP, rounded down.", + "shortDesc": "Pokemon making contact with this Pokemon lose 1/8 of their max HP.", + "onAfterDamageOrder": 1, + "id": "ironbarbs", + "name": "Iron Barbs", + "rating": 3, + "num": 160 + }, + "ironfist": { + "desc": "This Pokemon's punch-based attacks have their power multiplied by 1.2.", + "shortDesc": "This Pokemon's punch-based attacks have 1.2x power. Sucker Punch is not boosted.", + "onBasePowerPriority": 8, + "id": "ironfist", + "name": "Iron Fist", + "rating": 3, + "num": 89 + }, + "justified": { + "shortDesc": "This Pokemon's Attack is raised by 1 stage after it is damaged by a Dark-type move.", + "id": "justified", + "name": "Justified", + "rating": 2, + "num": 154 + }, + "keeneye": { + "desc": "Prevents other Pokemon from lowering this Pokemon's accuracy stat stage. This Pokemon ignores a target's evasiveness stat stage.", + "shortDesc": "This Pokemon's accuracy can't be lowered by others; ignores their evasiveness stat.", + "id": "keeneye", + "name": "Keen Eye", + "rating": 1, + "num": 51 + }, + "klutz": { + "desc": "This Pokemon's held item has no effect. This Pokemon cannot use Fling successfully. Macho Brace, Power Anklet, Power Band, Power Belt, Power Bracer, Power Lens, and Power Weight still have their effects.", + "shortDesc": "This Pokemon's held item has no effect, except Macho Brace. Fling cannot be used.", + "id": "klutz", + "name": "Klutz", + "rating": -1, + "num": 103 + }, + "leafguard": { + "desc": "If Sunny Day is active, this Pokemon cannot gain a major status condition and Rest will fail for it.", + "shortDesc": "If Sunny Day is active, this Pokemon cannot be statused and Rest will fail for it.", + "id": "leafguard", + "name": "Leaf Guard", + "rating": 1, + "num": 102 + }, + "levitate": { + "desc": "This Pokemon is immune to Ground. Gravity, Ingrain, Smack Down, Thousand Arrows, and Iron Ball nullify the immunity.", + "shortDesc": "This Pokemon is immune to Ground; Gravity/Ingrain/Smack Down/Iron Ball nullify it.", + "id": "levitate", + "name": "Levitate", + "rating": 3.5, + "num": 26 + }, + "lightmetal": { + "shortDesc": "This Pokemon's weight is halved.", + "id": "lightmetal", + "name": "Light Metal", + "rating": 1, + "num": 135 + }, + "lightningrod": { + "desc": "This Pokemon is immune to Electric-type moves and raises its Special Attack by 1 stage when hit by an Electric-type move. If this Pokemon is not the target of a single-target Electric-type move used by another Pokemon, this Pokemon redirects that move to itself if it is within the range of that move.", + "shortDesc": "This Pokemon draws Electric moves to itself to raise Sp. Atk by 1; Electric immunity.", + "id": "lightningrod", + "name": "Lightning Rod", + "rating": 3.5, + "num": 32 + }, + "limber": { + "shortDesc": "This Pokemon cannot be paralyzed. Gaining this Ability while paralyzed cures it.", + "id": "limber", + "name": "Limber", + "rating": 1.5, + "num": 7 + }, + "liquidooze": { + "shortDesc": "This Pokemon damages those draining HP from it for as much as they would heal.", + "id": "liquidooze", + "name": "Liquid Ooze", + "rating": 1.5, + "num": 64 + }, + "magicbounce": { + "desc": "This Pokemon blocks certain status moves and instead uses the move against the original user.", + "shortDesc": "This Pokemon blocks certain status moves and bounces them back to the user.", + "id": "magicbounce", + "name": "Magic Bounce", + "onTryHitPriority": 1, + "effect": { + "duration": 1 + }, + "rating": 4.5, + "num": 156 + }, + "magicguard": { + "desc": "This Pokemon can only be damaged by direct attacks. Curse and Substitute on use, Belly Drum, Pain Split, Struggle recoil, and confusion damage are considered direct damage.", + "shortDesc": "This Pokemon can only be damaged by direct attacks.", + "id": "magicguard", + "name": "Magic Guard", + "rating": 4.5, + "num": 98 + }, + "magician": { + "desc": "If this Pokemon has no item, it steals the item off a Pokemon it hits with an attack. Does not affect Doom Desire and Future Sight.", + "shortDesc": "If this Pokemon has no item, it steals the item off a Pokemon it hits with an attack.", + "id": "magician", + "name": "Magician", + "rating": 1.5, + "num": 170 + }, + "magmaarmor": { + "shortDesc": "This Pokemon cannot be frozen. Gaining this Ability while frozen cures it.", + "id": "magmaarmor", + "name": "Magma Armor", + "rating": 0.5, + "num": 40 + }, + "magnetpull": { + "desc": "Prevents adjacent opposing Steel-type Pokemon from choosing to switch out unless they are immune to trapping.", + "shortDesc": "Prevents adjacent Steel-type foes from choosing to switch.", + "id": "magnetpull", + "name": "Magnet Pull", + "rating": 4.5, + "num": 42 + }, + "marvelscale": { + "desc": "If this Pokemon has a major status condition, its Defense is multiplied by 1.5.", + "shortDesc": "If this Pokemon is statused, its Defense is 1.5x.", + "onModifyDefPriority": 6, + "id": "marvelscale", + "name": "Marvel Scale", + "rating": 2.5, + "num": 63 + }, + "megalauncher": { + "desc": "This Pokemon's pulse moves have their power multiplied by 1.5. Heal Pulse restores 3/4 of a target's maximum HP, rounded half down.", + "shortDesc": "This Pokemon's pulse moves have 1.5x power. Heal Pulse heals 3/4 target's max HP.", + "onBasePowerPriority": 8, + "id": "megalauncher", + "name": "Mega Launcher", + "rating": 3.5, + "num": 178 + }, + "minus": { + "desc": "If an active ally has this Ability or the Ability Plus, this Pokemon's Special Attack is multiplied by 1.5.", + "shortDesc": "If an active ally has this Ability or the Ability Plus, this Pokemon's Sp. Atk is 1.5x.", + "onModifySpAPriority": 5, + "id": "minus", + "name": "Minus", + "rating": 0, + "num": 58 + }, + "moldbreaker": { + "shortDesc": "This Pokemon's moves and their effects ignore the Abilities of other Pokemon.", + "stopAttackEvents": true, + "id": "moldbreaker", + "name": "Mold Breaker", + "rating": 3.5, + "num": 104 + }, + "moody": { + "desc": "This Pokemon has a random stat raised by 2 stages and another stat lowered by 1 stage at the end of each turn.", + "shortDesc": "Raises a random stat by 2 and lowers another stat by 1 at the end of each turn.", + "onResidualOrder": 26, + "onResidualSubOrder": 1, + "id": "moody", + "name": "Moody", + "rating": 5, + "num": 141 + }, + "motordrive": { + "desc": "This Pokemon is immune to Electric-type moves and raises its Speed by 1 stage when hit by an Electric-type move.", + "shortDesc": "This Pokemon's Speed is raised 1 stage if hit by an Electric move; Electric immunity.", + "id": "motordrive", + "name": "Motor Drive", + "rating": 3, + "num": 78 + }, + "moxie": { + "desc": "This Pokemon's Attack is raised by 1 stage if it attacks and knocks out another Pokemon.", + "shortDesc": "This Pokemon's Attack is raised by 1 stage if it attacks and KOes another Pokemon.", + "id": "moxie", + "name": "Moxie", + "rating": 3.5, + "num": 153 + }, + "multiscale": { + "shortDesc": "If this Pokemon is at full HP, damage taken from attacks is halved.", + "id": "multiscale", + "name": "Multiscale", + "rating": 4, + "num": 136 + }, + "multitype": { + "shortDesc": "If this Pokemon is an Arceus, its type changes to match its held Plate.", + "id": "multitype", + "name": "Multitype", + "rating": 4, + "num": 121 + }, + "mummy": { + "desc": "Pokemon making contact with this Pokemon have their Ability changed to Mummy. Does not affect the Abilities Multitype or Stance Change.", + "shortDesc": "Pokemon making contact with this Pokemon have their Ability changed to Mummy.", + "id": "mummy", + "name": "Mummy", + "rating": 2, + "num": 152 + }, + "naturalcure": { + "shortDesc": "This Pokemon has its major status condition cured when it switches out.", + "id": "naturalcure", + "name": "Natural Cure", + "rating": 3.5, + "num": 30 + }, + "noguard": { + "shortDesc": "Every move used by or against this Pokemon will always hit.", + "id": "noguard", + "name": "No Guard", + "rating": 4, + "num": 99 + }, + "normalize": { + "desc": "This Pokemon's moves are changed to be Normal type. This effect comes before other effects that change a move's type.", + "shortDesc": "This Pokemon's moves are changed to be Normal type.", + "onModifyMovePriority": 1, + "id": "normalize", + "name": "Normalize", + "rating": -1, + "num": 96 + }, + "oblivious": { + "desc": "This Pokemon cannot be infatuated or taunted. Gaining this Ability while affected cures it.", + "shortDesc": "This Pokemon cannot be infatuated or taunted. Gaining this Ability cures it.", + "id": "oblivious", + "name": "Oblivious", + "rating": 1, + "num": 12 + }, + "overcoat": { + "shortDesc": "This Pokemon is immune to powder moves and damage from Sandstorm or Hail.", + "id": "overcoat", + "name": "Overcoat", + "rating": 2.5, + "num": 142 + }, + "overgrow": { + "desc": "When this Pokemon has 1/3 or less of its maximum HP, its attacking stat is multiplied by 1.5 while using a Grass-type attack.", + "shortDesc": "When this Pokemon has 1/3 or less of its max HP, its Grass attacks do 1.5x damage.", + "onModifyAtkPriority": 5, + "onModifySpAPriority": 5, + "id": "overgrow", + "name": "Overgrow", + "rating": 2, + "num": 65 + }, + "owntempo": { + "shortDesc": "This Pokemon cannot be confused. Gaining this Ability while confused cures it.", + "id": "owntempo", + "name": "Own Tempo", + "rating": 1, + "num": 20 + }, + "parentalbond": { + "desc": "This Pokemon's damaging moves become multi-hit moves that hit twice. The second hit has its damage halved. Does not affect multi-hit moves or moves that have multiple targets.", + "shortDesc": "This Pokemon's damaging moves hit twice. The second hit has its damage halved.", + "effect": { + "duration": 1, + "onBasePowerPriority": 8 + }, + "id": "parentalbond", + "name": "Parental Bond", + "rating": 5, + "num": 184 + }, + "pickup": { + "shortDesc": "If this Pokemon has no item, it finds one used by an adjacent Pokemon this turn.", + "onResidualOrder": 26, + "onResidualSubOrder": 1, + "id": "pickup", + "name": "Pickup", + "rating": 0.5, + "num": 53 + }, + "pickpocket": { + "desc": "If this Pokemon has no item, it steals the item off a Pokemon that makes contact with it. This effect applies after all hits from a multi-hit move; Sheer Force prevents it from activating if the move has a secondary effect.", + "shortDesc": "If this Pokemon has no item, it steals the item off a Pokemon making contact with it.", + "id": "pickpocket", + "name": "Pickpocket", + "rating": 1, + "num": 124 + }, + "pixilate": { + "desc": "This Pokemon's Normal-type moves become Fairy-type moves and have their power multiplied by 1.3. This effect comes after other effects that change a move's type, but before Ion Deluge and Electrify's effects.", + "shortDesc": "This Pokemon's Normal-type moves become Fairy type and have 1.3x power.", + "onModifyMovePriority": -1, + "effect": { + "duration": 1, + "onBasePowerPriority": 8 + }, + "id": "pixilate", + "name": "Pixilate", + "rating": 4, + "num": 182 + }, + "plus": { + "desc": "If an active ally has this Ability or the Ability Minus, this Pokemon's Special Attack is multiplied by 1.5.", + "shortDesc": "If an active ally has this Ability or the Ability Minus, this Pokemon's Sp. Atk is 1.5x.", + "onModifySpAPriority": 5, + "id": "plus", + "name": "Plus", + "rating": 0, + "num": 57 + }, + "poisonheal": { + "desc": "If this Pokemon is poisoned, it restores 1/8 of its maximum HP, rounded down, at the end of each turn instead of losing HP.", + "shortDesc": "This Pokemon is healed by 1/8 of its max HP each turn when poisoned; no HP loss.", + "id": "poisonheal", + "name": "Poison Heal", + "rating": 4, + "num": 90 + }, + "poisonpoint": { + "shortDesc": "30% chance a Pokemon making contact with this Pokemon will be poisoned.", + "id": "poisonpoint", + "name": "Poison Point", + "rating": 2, + "num": 38 + }, + "poisontouch": { + "shortDesc": "This Pokemon's contact moves have a 30% chance of poisoning.", + "id": "poisontouch", + "name": "Poison Touch", + "rating": 2, + "num": 143 + }, + "prankster": { + "shortDesc": "This Pokemon's non-damaging moves have their priority increased by 1.", + "id": "prankster", + "name": "Prankster", + "rating": 4.5, + "num": 158 + }, + "pressure": { + "desc": "If this Pokemon is the target of an opposing Pokemon's move, that move loses one additional PP.", + "shortDesc": "If this Pokemon is the target of a foe's move, that move loses one additional PP.", + "id": "pressure", + "name": "Pressure", + "rating": 1.5, + "num": 46 + }, + "primordialsea": { + "desc": "On switch-in, the weather becomes heavy rain that prevents damaging Fire-type moves from executing, in addition to all the effects of Rain Dance. This weather remains in effect until this Ability is no longer active for any Pokemon, or the weather is changed by Delta Stream or Desolate Land.", + "shortDesc": "On switch-in, heavy rain begins until this Ability is not active in battle.", + "id": "primordialsea", + "name": "Primordial Sea", + "rating": 5, + "num": 189 + }, + "protean": { + "desc": "This Pokemon's type changes to match the type of the move it is about to use. This effect comes after all effects that change a move's type.", + "shortDesc": "This Pokemon's type changes to match the type of the move it is about to use.", + "id": "protean", + "name": "Protean", + "rating": 4, + "num": 168 + }, + "purepower": { + "shortDesc": "This Pokemon's Attack is doubled.", + "onModifyAtkPriority": 5, + "id": "purepower", + "name": "Pure Power", + "rating": 5, + "num": 74 + }, + "quickfeet": { + "desc": "If this Pokemon has a major status condition, its Speed is multiplied by 1.5; the Speed drop from paralysis is ignored.", + "shortDesc": "If this Pokemon is statused, its Speed is 1.5x; ignores Speed drop from paralysis.", + "id": "quickfeet", + "name": "Quick Feet", + "rating": 2.5, + "num": 95 + }, + "raindish": { + "desc": "If Rain Dance is active, this Pokemon restores 1/16 of its maximum HP, rounded down, at the end of each turn.", + "shortDesc": "If Rain Dance is active, this Pokemon heals 1/16 of its max HP each turn.", + "id": "raindish", + "name": "Rain Dish", + "rating": 1.5, + "num": 44 + }, + "rattled": { + "desc": "This Pokemon's Speed is raised by 1 stage if hit by a Bug-, Dark-, or Ghost-type attack.", + "shortDesc": "This Pokemon's Speed is raised 1 stage if hit by a Bug-, Dark-, or Ghost-type attack.", + "id": "rattled", + "name": "Rattled", + "rating": 1.5, + "num": 155 + }, + "reckless": { + "desc": "This Pokemon's attacks with recoil or crash damage have their power multiplied by 1.2. Does not affect Struggle.", + "shortDesc": "This Pokemon's attacks with recoil or crash damage have 1.2x power; not Struggle.", + "onBasePowerPriority": 8, + "id": "reckless", + "name": "Reckless", + "rating": 3, + "num": 120 + }, + "refrigerate": { + "desc": "This Pokemon's Normal-type moves become Ice-type moves and have their power multiplied by 1.3. This effect comes after other effects that change a move's type, but before Ion Deluge and Electrify's effects.", + "shortDesc": "This Pokemon's Normal-type moves become Ice type and have 1.3x power.", + "onModifyMovePriority": -1, + "effect": { + "duration": 1, + "onBasePowerPriority": 8 + }, + "id": "refrigerate", + "name": "Refrigerate", + "rating": 4, + "num": 174 + }, + "regenerator": { + "shortDesc": "This Pokemon restores 1/3 of its maximum HP, rounded down, when it switches out.", + "id": "regenerator", + "name": "Regenerator", + "rating": 4, + "num": 144 + }, + "rivalry": { + "desc": "This Pokemon's attacks have their power multiplied by 1.25 against targets of the same gender or multiplied by 0.75 against targets of the opposite gender. There is no modifier if either this Pokemon or the target is genderless.", + "shortDesc": "This Pokemon's attacks do 1.25x on same gender targets; 0.75x on opposite gender.", + "onBasePowerPriority": 8, + "id": "rivalry", + "name": "Rivalry", + "rating": 0.5, + "num": 79 + }, + "rockhead": { + "desc": "This Pokemon does not take recoil damage besides Struggle, Life Orb, and crash damage.", + "shortDesc": "This Pokemon does not take recoil damage besides Struggle/Life Orb/crash damage.", + "id": "rockhead", + "name": "Rock Head", + "rating": 3, + "num": 69 + }, + "roughskin": { + "desc": "Pokemon making contact with this Pokemon lose 1/8 of their maximum HP, rounded down.", + "shortDesc": "Pokemon making contact with this Pokemon lose 1/8 of their max HP.", + "onAfterDamageOrder": 1, + "id": "roughskin", + "name": "Rough Skin", + "rating": 3, + "num": 24 + }, + "runaway": { + "shortDesc": "No competitive use.", + "id": "runaway", + "name": "Run Away", + "rating": 0, + "num": 50 + }, + "sandforce": { + "desc": "If Sandstorm is active, this Pokemon's Ground-, Rock-, and Steel-type attacks have their power multiplied by 1.3. This Pokemon takes no damage from Sandstorm.", + "shortDesc": "This Pokemon's Ground/Rock/Steel attacks do 1.3x in Sandstorm; immunity to it.", + "onBasePowerPriority": 8, + "id": "sandforce", + "name": "Sand Force", + "rating": 2, + "num": 159 + }, + "sandrush": { + "desc": "If Sandstorm is active, this Pokemon's Speed is doubled. This Pokemon takes no damage from Sandstorm.", + "shortDesc": "If Sandstorm is active, this Pokemon's Speed is doubled; immunity to Sandstorm.", + "id": "sandrush", + "name": "Sand Rush", + "rating": 2.5, + "num": 146 + }, + "sandstream": { + "shortDesc": "On switch-in, this Pokemon summons Sandstorm.", + "id": "sandstream", + "name": "Sand Stream", + "rating": 4.5, + "num": 45 + }, + "sandveil": { + "desc": "If Sandstorm is active, this Pokemon's evasiveness is multiplied by 1.25. This Pokemon takes no damage from Sandstorm.", + "shortDesc": "If Sandstorm is active, this Pokemon's evasiveness is 1.25x; immunity to Sandstorm.", + "id": "sandveil", + "name": "Sand Veil", + "rating": 1.5, + "num": 8 + }, + "sapsipper": { + "desc": "This Pokemon is immune to Grass-type moves and raises its Attack by 1 stage when hit by a Grass-type move.", + "shortDesc": "This Pokemon's Attack is raised 1 stage if hit by a Grass move; Grass immunity.", + "id": "sapsipper", + "name": "Sap Sipper", + "rating": 3.5, + "num": 157 + }, + "scrappy": { + "shortDesc": "This Pokemon can hit Ghost types with Normal- and Fighting-type moves.", + "onModifyMovePriority": -5, + "id": "scrappy", + "name": "Scrappy", + "rating": 3, + "num": 113 + }, + "serenegrace": { + "shortDesc": "This Pokemon's moves have their secondary effect chance doubled.", + "onModifyMovePriority": -2, + "id": "serenegrace", + "name": "Serene Grace", + "rating": 4, + "num": 32 + }, + "shadowtag": { + "desc": "Prevents adjacent opposing Pokemon from choosing to switch out unless they are immune to trapping or also have this Ability.", + "shortDesc": "Prevents adjacent foes from choosing to switch unless they also have this Ability.", + "id": "shadowtag", + "name": "Shadow Tag", + "rating": 5, + "num": 23 + }, + "shedskin": { + "desc": "This Pokemon has a 33% chance to have its major status condition cured at the end of each turn.", + "shortDesc": "This Pokemon has a 33% chance to have its status cured at the end of each turn.", + "onResidualOrder": 5, + "onResidualSubOrder": 1, + "id": "shedskin", + "name": "Shed Skin", + "rating": 3.5, + "num": 61 + }, + "sheerforce": { + "desc": "This Pokemon's attacks with secondary effects have their power multiplied by 1.3, but the secondary effects are removed.", + "shortDesc": "This Pokemon's attacks with secondary effects have 1.3x power; nullifies the effects.", + "effect": { + "duration": 1, + "onBasePowerPriority": 8 + }, + "id": "sheerforce", + "name": "Sheer Force", + "rating": 4, + "num": 125 + }, + "shellarmor": { + "shortDesc": "This Pokemon cannot be struck by a critical hit.", + "onCriticalHit": false, + "id": "shellarmor", + "name": "Shell Armor", + "rating": 1, + "num": 75 + }, + "shielddust": { + "shortDesc": "This Pokemon is not affected by the secondary effect of another Pokemon's attack.", + "id": "shielddust", + "name": "Shield Dust", + "rating": 2.5, + "num": 19 + }, + "simple": { + "shortDesc": "If this Pokemon's stat stages are raised or lowered, the effect is doubled instead.", + "id": "simple", + "name": "Simple", + "rating": 4, + "num": 86 + }, + "skilllink": { + "shortDesc": "This Pokemon's multi-hit attacks always hit the maximum number of times.", + "id": "skilllink", + "name": "Skill Link", + "rating": 4, + "num": 92 + }, + "slowstart": { + "shortDesc": "On switch-in, this Pokemon's Attack and Speed are halved for 5 turns.", + "effect": { + "duration": 5, + "onModifyAtkPriority": 5 + }, + "id": "slowstart", + "name": "Slow Start", + "rating": -2, + "num": 112 + }, + "sniper": { + "shortDesc": "If this Pokemon strikes with a critical hit, the damage is multiplied by 1.5.", + "id": "sniper", + "name": "Sniper", + "rating": 1, + "num": 97 + }, + "snowcloak": { + "desc": "If Hail is active, this Pokemon's evasiveness is multiplied by 1.25. This Pokemon takes no damage from Hail.", + "shortDesc": "If Hail is active, this Pokemon's evasiveness is 1.25x; immunity to Hail.", + "id": "snowcloak", + "name": "Snow Cloak", + "rating": 1.5, + "num": 81 + }, + "snowwarning": { + "shortDesc": "On switch-in, this Pokemon summons Hail.", + "id": "snowwarning", + "name": "Snow Warning", + "rating": 4, + "num": 117 + }, + "solarpower": { + "desc": "If Sunny Day is active, this Pokemon's Special Attack is multiplied by 1.5 and it loses 1/8 of its maximum HP, rounded down, at the end of each turn.", + "shortDesc": "If Sunny Day is active, this Pokemon's Sp. Atk is 1.5x; loses 1/8 max HP per turn.", + "onModifySpAPriority": 5, + "id": "solarpower", + "name": "Solar Power", + "rating": 1.5, + "num": 94 + }, + "solidrock": { + "shortDesc": "This Pokemon receives 3/4 damage from supereffective attacks.", + "id": "solidrock", + "name": "Solid Rock", + "rating": 3, + "num": 116 + }, + "soundproof": { + "shortDesc": "This Pokemon is immune to sound-based moves, including Heal Bell.", + "id": "soundproof", + "name": "Soundproof", + "rating": 2, + "num": 43 + }, + "speedboost": { + "desc": "This Pokemon's Speed is raised by 1 stage at the end of each full turn it has been on the field.", + "shortDesc": "This Pokemon's Speed is raised 1 stage at the end of each full turn on the field.", + "onResidualOrder": 26, + "onResidualSubOrder": 1, + "id": "speedboost", + "name": "Speed Boost", + "rating": 4.5, + "num": 3 + }, + "stall": { + "shortDesc": "This Pokemon moves last among Pokemon using the same or greater priority moves.", + "id": "stall", + "name": "Stall", + "rating": -1, + "num": 100 + }, + "stancechange": { + "desc": "If this Pokemon is an Aegislash, it changes to Blade Forme before attempting to use an attacking move, and changes to Shield Forme before attempting to use King's Shield.", + "shortDesc": "If Aegislash, changes Forme to Blade before attacks and Shield before King's Shield.", + "onBeforeMovePriority": 11, + "id": "stancechange", + "name": "Stance Change", + "rating": 5, + "num": 176 + }, + "static": { + "shortDesc": "30% chance a Pokemon making contact with this Pokemon will be paralyzed.", + "id": "static", + "name": "Static", + "rating": 2, + "num": 9 + }, + "steadfast": { + "shortDesc": "If this Pokemon flinches, its Speed is raised by 1 stage.", + "id": "steadfast", + "name": "Steadfast", + "rating": 1, + "num": 80 + }, + "stench": { + "shortDesc": "This Pokemon's attacks without a chance to flinch have a 10% chance to flinch.", + "id": "stench", + "name": "Stench", + "rating": 0.5, + "num": 1 + }, + "stickyhold": { + "shortDesc": "This Pokemon cannot lose its held item due to another Pokemon's attack.", + "id": "stickyhold", + "name": "Sticky Hold", + "rating": 1.5, + "num": 60 + }, + "stormdrain": { + "desc": "This Pokemon is immune to Water-type moves and raises its Special Attack by 1 stage when hit by a Water-type move. If this Pokemon is not the target of a single-target Water-type move used by another Pokemon, this Pokemon redirects that move to itself if it is within the range of that move.", + "shortDesc": "This Pokemon draws Water moves to itself to raise Sp. Atk by 1; Water immunity.", + "id": "stormdrain", + "name": "Storm Drain", + "rating": 3.5, + "num": 114 + }, + "strongjaw": { + "desc": "This Pokemon's bite-based attacks have their power multiplied by 1.5.", + "shortDesc": "This Pokemon's bite-based attacks have 1.5x power. Bug Bite is not boosted.", + "onBasePowerPriority": 8, + "id": "strongjaw", + "name": "Strong Jaw", + "rating": 3, + "num": 173 + }, + "sturdy": { + "desc": "If this Pokemon is at full HP, it survives one hit with at least 1 HP. OHKO moves fail when used against this Pokemon.", + "shortDesc": "If this Pokemon is at full HP, it survives one hit with at least 1 HP. Immune to OHKO.", + "onDamagePriority": -100, + "id": "sturdy", + "name": "Sturdy", + "rating": 3, + "num": 5 + }, + "suctioncups": { + "shortDesc": "This Pokemon cannot be forced to switch out by another Pokemon's attack or item.", + "onDragOutPriority": 1, + "id": "suctioncups", + "name": "Suction Cups", + "rating": 2, + "num": 21 + }, + "superluck": { + "shortDesc": "This Pokemon's critical hit ratio is raised by 1 stage.", + "id": "superluck", + "name": "Super Luck", + "rating": 1.5, + "num": 105 + }, + "swarm": { + "desc": "When this Pokemon has 1/3 or less of its maximum HP, rounded down, its attacking stat is multiplied by 1.5 while using a Bug-type attack.", + "shortDesc": "When this Pokemon has 1/3 or less of its max HP, its Bug attacks do 1.5x damage.", + "onModifyAtkPriority": 5, + "onModifySpAPriority": 5, + "id": "swarm", + "name": "Swarm", + "rating": 2, + "num": 68 + }, + "sweetveil": { + "shortDesc": "This Pokemon and its allies cannot fall asleep.", + "id": "sweetveil", + "name": "Sweet Veil", + "rating": 2, + "num": 175 + }, + "swiftswim": { + "shortDesc": "If Rain Dance is active, this Pokemon's Speed is doubled.", + "id": "swiftswim", + "name": "Swift Swim", + "rating": 2.5, + "num": 33 + }, + "symbiosis": { + "desc": "If an ally uses its item, this Pokemon gives its item to that ally immediately. Does not activate if the ally's item was stolen or knocked off.", + "shortDesc": "If an ally uses its item, this Pokemon gives its item to that ally immediately.", + "id": "symbiosis", + "name": "Symbiosis", + "rating": 0, + "num": 180 + }, + "synchronize": { + "desc": "If another Pokemon burns, paralyzes, poisons, or badly poisons this Pokemon, that Pokemon receives the same major status condition.", + "shortDesc": "If another Pokemon burns/poisons/paralyzes this Pokemon, it also gets that status.", + "id": "synchronize", + "name": "Synchronize", + "rating": 2.5, + "num": 28 + }, + "tangledfeet": { + "shortDesc": "This Pokemon's evasiveness is doubled as long as it is confused.", + "id": "tangledfeet", + "name": "Tangled Feet", + "rating": 1, + "num": 77 + }, + "technician": { + "desc": "This Pokemon's moves of 60 power or less have their power multiplied by 1.5. Does affect Struggle.", + "shortDesc": "This Pokemon's moves of 60 power or less have 1.5x power. Includes Struggle.", + "onBasePowerPriority": 8, + "id": "technician", + "name": "Technician", + "rating": 4, + "num": 101 + }, + "telepathy": { + "shortDesc": "This Pokemon does not take damage from attacks made by its allies.", + "id": "telepathy", + "name": "Telepathy", + "rating": 0, + "num": 140 + }, + "teravolt": { + "shortDesc": "This Pokemon's moves and their effects ignore the Abilities of other Pokemon.", + "stopAttackEvents": true, + "id": "teravolt", + "name": "Teravolt", + "rating": 3.5, + "num": 164 + }, + "thickfat": { + "desc": "If a Pokemon uses a Fire- or Ice-type attack against this Pokemon, that Pokemon's attacking stat is halved when calculating the damage to this Pokemon.", + "shortDesc": "Fire/Ice-type moves against this Pokemon deal damage with a halved attacking stat.", + "onModifyAtkPriority": 6, + "onModifySpAPriority": 5, + "id": "thickfat", + "name": "Thick Fat", + "rating": 3.5, + "num": 47 + }, + "tintedlens": { + "shortDesc": "This Pokemon's attacks that are not very effective on a target deal double damage.", + "id": "tintedlens", + "name": "Tinted Lens", + "rating": 3.5, + "num": 110 + }, + "torrent": { + "desc": "When this Pokemon has 1/3 or less of its maximum HP, rounded down, its attacking stat is multiplied by 1.5 while using a Water-type attack.", + "shortDesc": "When this Pokemon has 1/3 or less of its max HP, its Water attacks do 1.5x damage.", + "onModifyAtkPriority": 5, + "onModifySpAPriority": 5, + "id": "torrent", + "name": "Torrent", + "rating": 2, + "num": 67 + }, + "toxicboost": { + "desc": "While this Pokemon is poisoned, the power of its physical attacks is multiplied by 1.5.", + "shortDesc": "While this Pokemon is poisoned, its physical attacks have 1.5x power.", + "onBasePowerPriority": 8, + "id": "toxicboost", + "name": "Toxic Boost", + "rating": 3, + "num": 137 + }, + "toughclaws": { + "shortDesc": "This Pokemon's contact moves have their power multiplied by 1.3.", + "onBasePowerPriority": 8, + "id": "toughclaws", + "name": "Tough Claws", + "rating": 3.5, + "num": 181 + }, + "trace": { + "desc": "On switch-in, this Pokemon copies a random adjacent opposing Pokemon's Ability. If there is no Ability that can be copied at that time, this Ability will activate as soon as an Ability can be copied. Abilities that cannot be copied are Flower Gift, Forecast, Illusion, Imposter, Multitype, Stance Change, Trace, and Zen Mode.", + "shortDesc": "On switch-in, or when it can, this Pokemon copies a random adjacent foe's Ability.", + "id": "trace", + "name": "Trace", + "rating": 3, + "num": 36 + }, + "truant": { + "shortDesc": "This Pokemon skips every other turn instead of using a move.", + "onBeforeMovePriority": 9, + "effect": { + "duration": 2 + }, + "id": "truant", + "name": "Truant", + "rating": -2, + "num": 54 + }, + "turboblaze": { + "shortDesc": "This Pokemon's moves and their effects ignore the Abilities of other Pokemon.", + "stopAttackEvents": true, + "id": "turboblaze", + "name": "Turboblaze", + "rating": 3.5, + "num": 163 + }, + "unaware": { + "desc": "This Pokemon ignores other Pokemon's Attack, Special Attack, and accuracy stat stages when taking damage, and ignores other Pokemon's Defense, Special Defense, and evasiveness stat stages when dealing damage.", + "shortDesc": "This Pokemon ignores other Pokemon's stat stages when taking or doing damage.", + "id": "unaware", + "name": "Unaware", + "rating": 3, + "num": 109 + }, + "unburden": { + "desc": "If this Pokemon loses its held item for any reason, its Speed is doubled. This boost is lost if it switches out or gains a new item or Ability.", + "shortDesc": "Speed is doubled on held item loss; boost is lost if it switches, gets new item/Ability.", + "effect": {}, + "id": "unburden", + "name": "Unburden", + "rating": 3.5, + "num": 84 + }, + "unnerve": { + "shortDesc": "While this Pokemon is active, it prevents opposing Pokemon from using their Berries.", + "onFoeTryEatItem": false, + "id": "unnerve", + "name": "Unnerve", + "rating": 1.5, + "num": 127 + }, + "victorystar": { + "shortDesc": "This Pokemon and its allies' moves have their accuracy multiplied by 1.1.", + "id": "victorystar", + "name": "Victory Star", + "rating": 2.5, + "num": 162 + }, + "vitalspirit": { + "shortDesc": "This Pokemon cannot fall asleep. Gaining this Ability while asleep cures it.", + "id": "vitalspirit", + "name": "Vital Spirit", + "rating": 2, + "num": 72 + }, + "voltabsorb": { + "desc": "This Pokemon is immune to Electric-type moves and restores 1/4 of its maximum HP, rounded down, when hit by an Electric-type move.", + "shortDesc": "This Pokemon heals 1/4 of its max HP when hit by Electric moves; Electric immunity.", + "id": "voltabsorb", + "name": "Volt Absorb", + "rating": 3.5, + "num": 10 + }, + "waterabsorb": { + "desc": "This Pokemon is immune to Water-type moves and restores 1/4 of its maximum HP, rounded down, when hit by a Water-type move.", + "shortDesc": "This Pokemon heals 1/4 of its max HP when hit by Water moves; Water immunity.", + "id": "waterabsorb", + "name": "Water Absorb", + "rating": 3.5, + "num": 11 + }, + "waterveil": { + "shortDesc": "This Pokemon cannot be burned. Gaining this Ability while burned cures it.", + "id": "waterveil", + "name": "Water Veil", + "rating": 2, + "num": 41 + }, + "weakarmor": { + "desc": "If a physical attack hits this Pokemon, its Defense is lowered by 1 stage and its Speed is raised by 1 stage.", + "shortDesc": "If a physical attack hits this Pokemon, Defense is lowered by 1, Speed is raised by 1.", + "id": "weakarmor", + "name": "Weak Armor", + "rating": 0.5, + "num": 133 + }, + "whitesmoke": { + "shortDesc": "Prevents other Pokemon from lowering this Pokemon's stat stages.", + "id": "whitesmoke", + "name": "White Smoke", + "rating": 2, + "num": 73 + }, + "wonderguard": { + "shortDesc": "This Pokemon can only be damaged by supereffective moves and indirect damage.", + "id": "wonderguard", + "name": "Wonder Guard", + "rating": 5, + "num": 25 + }, + "wonderskin": { + "desc": "All non-damaging moves that check accuracy have their accuracy changed to 50% when used on this Pokemon. This change is done before any other accuracy modifying effects.", + "shortDesc": "Status moves with accuracy checks are 50% accurate when used on this Pokemon.", + "onModifyAccuracyPriority": 10, + "id": "wonderskin", + "name": "Wonder Skin", + "rating": 2, + "num": 147 + }, + "zenmode": { + "desc": "If this Pokemon is a Darmanitan, it changes to Zen Mode if it has 1/2 or less of its maximum HP at the end of a turn. If Darmanitan's HP is above 1/2 of its maximum HP at the end of a turn, it changes back to Standard Mode. If Darmanitan loses this Ability while in Zen Mode it reverts to Standard Mode immediately.", + "shortDesc": "If Darmanitan, at end of turn changes Mode to Standard if > 1/2 max HP, else Zen.", + "onResidualOrder": 27, + "effect": {}, + "id": "zenmode", + "name": "Zen Mode", + "rating": -1, + "num": 161 + }, + "mountaineer": { + "shortDesc": "On switch-in, this Pokemon avoids all Rock-type attacks and Stealth Rock.", + "id": "mountaineer", + "isNonstandard": true, + "name": "Mountaineer", + "rating": 3.5, + "num": -2 + }, + "rebound": { + "desc": "On switch-in, this Pokemon blocks certain status moves and instead uses the move against the original user.", + "shortDesc": "On switch-in, blocks certain status moves and bounces them back to the user.", + "id": "rebound", + "isNonstandard": true, + "name": "Rebound", + "onTryHitPriority": 1, + "effect": { + "duration": 1 + }, + "rating": 3.5, + "num": -3 + }, + "persistent": { + "shortDesc": "The duration of certain field effects is increased by 2 turns if used by this Pokemon.", + "id": "persistent", + "isNonstandard": true, + "name": "Persistent", + "rating": 3.5, + "num": -4 + } +} \ No newline at end of file diff --git a/WizBot/bin/Debug/data/pokemon/pokemon_list.json b/WizBot/bin/Debug/data/pokemon/pokemon_list.json new file mode 100644 index 000000000..d93b3557d --- /dev/null +++ b/WizBot/bin/Debug/data/pokemon/pokemon_list.json @@ -0,0 +1,25607 @@ +{ + "bulbasaur": { + "num": 1, + "species": "Bulbasaur", + "types": [ + "Grass", + "Poison" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 45, + "atk": 49, + "def": 49, + "spa": 65, + "spd": 65, + "spe": 45 + }, + "abilities": { + "0": "Overgrow", + "H": "Chlorophyll" + }, + "heightm": 0.7, + "weightkg": 6.9, + "color": "Green", + "evos": [ + "ivysaur" + ], + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "ivysaur": { + "num": 2, + "species": "Ivysaur", + "types": [ + "Grass", + "Poison" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 60, + "atk": 62, + "def": 63, + "spa": 80, + "spd": 80, + "spe": 60 + }, + "abilities": { + "0": "Overgrow", + "H": "Chlorophyll" + }, + "heightm": 1, + "weightkg": 13, + "color": "Green", + "prevo": "bulbasaur", + "evos": [ + "venusaur" + ], + "evoLevel": 16, + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "venusaur": { + "num": 3, + "species": "Venusaur", + "types": [ + "Grass", + "Poison" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 80, + "atk": 82, + "def": 83, + "spa": 100, + "spd": 100, + "spe": 80 + }, + "abilities": { + "0": "Overgrow", + "H": "Chlorophyll" + }, + "heightm": 2, + "weightkg": 100, + "color": "Green", + "prevo": "ivysaur", + "evoLevel": 32, + "eggGroups": [ + "Monster", + "Grass" + ], + "otherFormes": [ + "venusaurmega" + ] + }, + "venusaurmega": { + "num": 3, + "species": "Venusaur-Mega", + "baseSpecies": "Venusaur", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Grass", + "Poison" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 80, + "atk": 100, + "def": 123, + "spa": 122, + "spd": 120, + "spe": 80 + }, + "abilities": { + "0": "Thick Fat" + }, + "heightm": 2.4, + "weightkg": 155.5, + "color": "Green", + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "charmander": { + "num": 4, + "species": "Charmander", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 39, + "atk": 52, + "def": 43, + "spa": 60, + "spd": 50, + "spe": 65 + }, + "abilities": { + "0": "Blaze", + "H": "Solar Power" + }, + "heightm": 0.6, + "weightkg": 8.5, + "color": "Red", + "evos": [ + "charmeleon" + ], + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "charmeleon": { + "num": 5, + "species": "Charmeleon", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 58, + "atk": 64, + "def": 58, + "spa": 80, + "spd": 65, + "spe": 80 + }, + "abilities": { + "0": "Blaze", + "H": "Solar Power" + }, + "heightm": 1.1, + "weightkg": 19, + "color": "Red", + "prevo": "charmander", + "evos": [ + "charizard" + ], + "evoLevel": 16, + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "charizard": { + "num": 6, + "species": "Charizard", + "types": [ + "Fire", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 78, + "atk": 84, + "def": 78, + "spa": 109, + "spd": 85, + "spe": 100 + }, + "abilities": { + "0": "Blaze", + "H": "Solar Power" + }, + "heightm": 1.7, + "weightkg": 90.5, + "color": "Red", + "prevo": "charmeleon", + "evoLevel": 36, + "eggGroups": [ + "Monster", + "Dragon" + ], + "otherFormes": [ + "charizardmegax", + "charizardmegay" + ] + }, + "charizardmegax": { + "num": 6, + "species": "Charizard-Mega-X", + "baseSpecies": "Charizard", + "forme": "Mega-X", + "formeLetter": "M", + "types": [ + "Fire", + "Dragon" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 78, + "atk": 130, + "def": 111, + "spa": 130, + "spd": 85, + "spe": 100 + }, + "abilities": { + "0": "Tough Claws" + }, + "heightm": 1.7, + "weightkg": 110.5, + "color": "Red", + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "charizardmegay": { + "num": 6, + "species": "Charizard-Mega-Y", + "baseSpecies": "Charizard", + "forme": "Mega-Y", + "formeLetter": "M", + "types": [ + "Fire", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 78, + "atk": 104, + "def": 78, + "spa": 159, + "spd": 115, + "spe": 100 + }, + "abilities": { + "0": "Drought" + }, + "heightm": 1.7, + "weightkg": 100.5, + "color": "Red", + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "squirtle": { + "num": 7, + "species": "Squirtle", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 44, + "atk": 48, + "def": 65, + "spa": 50, + "spd": 64, + "spe": 43 + }, + "abilities": { + "0": "Torrent", + "H": "Rain Dish" + }, + "heightm": 0.5, + "weightkg": 9, + "color": "Blue", + "evos": [ + "wartortle" + ], + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "wartortle": { + "num": 8, + "species": "Wartortle", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 59, + "atk": 63, + "def": 80, + "spa": 65, + "spd": 80, + "spe": 58 + }, + "abilities": { + "0": "Torrent", + "H": "Rain Dish" + }, + "heightm": 1, + "weightkg": 22.5, + "color": "Blue", + "prevo": "squirtle", + "evos": [ + "blastoise" + ], + "evoLevel": 16, + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "blastoise": { + "num": 9, + "species": "Blastoise", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 79, + "atk": 83, + "def": 100, + "spa": 85, + "spd": 105, + "spe": 78 + }, + "abilities": { + "0": "Torrent", + "H": "Rain Dish" + }, + "heightm": 1.6, + "weightkg": 85.5, + "color": "Blue", + "prevo": "wartortle", + "evoLevel": 36, + "eggGroups": [ + "Monster", + "Water 1" + ], + "otherFormes": [ + "blastoisemega" + ] + }, + "blastoisemega": { + "num": 9, + "species": "Blastoise-Mega", + "baseSpecies": "Blastoise", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 79, + "atk": 103, + "def": 120, + "spa": 135, + "spd": 115, + "spe": 78 + }, + "abilities": { + "0": "Mega Launcher" + }, + "heightm": 1.6, + "weightkg": 101.1, + "color": "Blue", + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "caterpie": { + "num": 10, + "species": "Caterpie", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 45, + "atk": 30, + "def": 35, + "spa": 20, + "spd": 20, + "spe": 45 + }, + "abilities": { + "0": "Shield Dust", + "H": "Run Away" + }, + "heightm": 0.3, + "weightkg": 2.9, + "color": "Green", + "evos": [ + "metapod" + ], + "eggGroups": [ + "Bug" + ] + }, + "metapod": { + "num": 11, + "species": "Metapod", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 50, + "atk": 20, + "def": 55, + "spa": 25, + "spd": 25, + "spe": 30 + }, + "abilities": { + "0": "Shed Skin" + }, + "heightm": 0.7, + "weightkg": 9.9, + "color": "Green", + "prevo": "caterpie", + "evos": [ + "butterfree" + ], + "evoLevel": 7, + "eggGroups": [ + "Bug" + ] + }, + "butterfree": { + "num": 12, + "species": "Butterfree", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 60, + "atk": 45, + "def": 50, + "spa": 90, + "spd": 80, + "spe": 70 + }, + "abilities": { + "0": "Compound Eyes", + "H": "Tinted Lens" + }, + "heightm": 1.1, + "weightkg": 32, + "color": "White", + "prevo": "metapod", + "evoLevel": 10, + "eggGroups": [ + "Bug" + ] + }, + "weedle": { + "num": 13, + "species": "Weedle", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 40, + "atk": 35, + "def": 30, + "spa": 20, + "spd": 20, + "spe": 50 + }, + "abilities": { + "0": "Shield Dust", + "H": "Run Away" + }, + "heightm": 0.3, + "weightkg": 3.2, + "color": "Brown", + "evos": [ + "kakuna" + ], + "eggGroups": [ + "Bug" + ] + }, + "kakuna": { + "num": 14, + "species": "Kakuna", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 45, + "atk": 25, + "def": 50, + "spa": 25, + "spd": 25, + "spe": 35 + }, + "abilities": { + "0": "Shed Skin" + }, + "heightm": 0.6, + "weightkg": 10, + "color": "Yellow", + "prevo": "weedle", + "evos": [ + "beedrill" + ], + "evoLevel": 7, + "eggGroups": [ + "Bug" + ] + }, + "beedrill": { + "num": 15, + "species": "Beedrill", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 65, + "atk": 90, + "def": 40, + "spa": 45, + "spd": 80, + "spe": 75 + }, + "abilities": { + "0": "Swarm", + "H": "Sniper" + }, + "heightm": 1, + "weightkg": 29.5, + "color": "Yellow", + "prevo": "kakuna", + "evoLevel": 10, + "eggGroups": [ + "Bug" + ], + "otherFormes": [ + "beedrillmega" + ] + }, + "beedrillmega": { + "num": 15, + "species": "Beedrill-Mega", + "baseSpecies": "Beedrill", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 65, + "atk": 150, + "def": 40, + "spa": 15, + "spd": 80, + "spe": 145 + }, + "abilities": { + "0": "Adaptability" + }, + "heightm": 1.4, + "weightkg": 40.5, + "color": "Yellow", + "eggGroups": [ + "Bug" + ] + }, + "pidgey": { + "num": 16, + "species": "Pidgey", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 40, + "atk": 45, + "def": 40, + "spa": 35, + "spd": 35, + "spe": 56 + }, + "abilities": { + "0": "Keen Eye", + "1": "Tangled Feet", + "H": "Big Pecks" + }, + "heightm": 0.3, + "weightkg": 1.8, + "color": "Brown", + "evos": [ + "pidgeotto" + ], + "eggGroups": [ + "Flying" + ] + }, + "pidgeotto": { + "num": 17, + "species": "Pidgeotto", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 63, + "atk": 60, + "def": 55, + "spa": 50, + "spd": 50, + "spe": 71 + }, + "abilities": { + "0": "Keen Eye", + "1": "Tangled Feet", + "H": "Big Pecks" + }, + "heightm": 1.1, + "weightkg": 30, + "color": "Brown", + "prevo": "pidgey", + "evos": [ + "pidgeot" + ], + "evoLevel": 18, + "eggGroups": [ + "Flying" + ] + }, + "pidgeot": { + "num": 18, + "species": "Pidgeot", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 83, + "atk": 80, + "def": 75, + "spa": 70, + "spd": 70, + "spe": 101 + }, + "abilities": { + "0": "Keen Eye", + "1": "Tangled Feet", + "H": "Big Pecks" + }, + "heightm": 1.5, + "weightkg": 39.5, + "color": "Brown", + "prevo": "pidgeotto", + "evoLevel": 36, + "eggGroups": [ + "Flying" + ], + "otherFormes": [ + "pidgeotmega" + ] + }, + "pidgeotmega": { + "num": 18, + "species": "Pidgeot-Mega", + "baseSpecies": "Pidgeot", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 83, + "atk": 80, + "def": 80, + "spa": 135, + "spd": 80, + "spe": 121 + }, + "abilities": { + "0": "No Guard" + }, + "heightm": 2.2, + "weightkg": 50.5, + "color": "Brown", + "eggGroups": [ + "Flying" + ] + }, + "rattata": { + "num": 19, + "species": "Rattata", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 30, + "atk": 56, + "def": 35, + "spa": 25, + "spd": 35, + "spe": 72 + }, + "abilities": { + "0": "Run Away", + "1": "Guts", + "H": "Hustle" + }, + "heightm": 0.3, + "weightkg": 3.5, + "color": "Purple", + "evos": [ + "raticate" + ], + "eggGroups": [ + "Field" + ] + }, + "raticate": { + "num": 20, + "species": "Raticate", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 55, + "atk": 81, + "def": 60, + "spa": 50, + "spd": 70, + "spe": 97 + }, + "abilities": { + "0": "Run Away", + "1": "Guts", + "H": "Hustle" + }, + "heightm": 0.7, + "weightkg": 18.5, + "color": "Brown", + "prevo": "rattata", + "evoLevel": 20, + "eggGroups": [ + "Field" + ] + }, + "spearow": { + "num": 21, + "species": "Spearow", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 40, + "atk": 60, + "def": 30, + "spa": 31, + "spd": 31, + "spe": 70 + }, + "abilities": { + "0": "Keen Eye", + "H": "Sniper" + }, + "heightm": 0.3, + "weightkg": 2, + "color": "Brown", + "evos": [ + "fearow" + ], + "eggGroups": [ + "Flying" + ] + }, + "fearow": { + "num": 22, + "species": "Fearow", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 65, + "atk": 90, + "def": 65, + "spa": 61, + "spd": 61, + "spe": 100 + }, + "abilities": { + "0": "Keen Eye", + "H": "Sniper" + }, + "heightm": 1.2, + "weightkg": 38, + "color": "Brown", + "prevo": "spearow", + "evoLevel": 20, + "eggGroups": [ + "Flying" + ] + }, + "ekans": { + "num": 23, + "species": "Ekans", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 35, + "atk": 60, + "def": 44, + "spa": 40, + "spd": 54, + "spe": 55 + }, + "abilities": { + "0": "Intimidate", + "1": "Shed Skin", + "H": "Unnerve" + }, + "heightm": 2, + "weightkg": 6.9, + "color": "Purple", + "evos": [ + "arbok" + ], + "eggGroups": [ + "Field", + "Dragon" + ] + }, + "arbok": { + "num": 24, + "species": "Arbok", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 85, + "def": 69, + "spa": 65, + "spd": 79, + "spe": 80 + }, + "abilities": { + "0": "Intimidate", + "1": "Shed Skin", + "H": "Unnerve" + }, + "heightm": 3.5, + "weightkg": 65, + "color": "Purple", + "prevo": "ekans", + "evoLevel": 22, + "eggGroups": [ + "Field", + "Dragon" + ] + }, + "pikachu": { + "num": 25, + "species": "Pikachu", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 35, + "atk": 55, + "def": 40, + "spa": 50, + "spd": 50, + "spe": 90 + }, + "abilities": { + "0": "Static", + "H": "Lightning Rod" + }, + "heightm": 0.4, + "weightkg": 6, + "color": "Yellow", + "prevo": "pichu", + "evos": [ + "raichu" + ], + "evoLevel": 1, + "eggGroups": [ + "Field", + "Fairy" + ], + "otherFormes": [ + "pikachucosplay", + "pikachurockstar", + "pikachubelle", + "pikachupopstar", + "pikachuphd", + "pikachulibre" + ] + }, + "pikachucosplay": { + "num": 25, + "species": "Pikachu-Cosplay", + "baseSpecies": "Pikachu", + "forme": "Cosplay", + "formeLetter": "C", + "types": [ + "Electric" + ], + "gender": "F", + "baseStats": { + "hp": 35, + "atk": 55, + "def": 40, + "spa": 50, + "spd": 50, + "spe": 90 + }, + "abilities": { + "0": "Lightning Rod" + }, + "heightm": 0.4, + "weightkg": 6, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "pikachurockstar": { + "num": 25, + "species": "Pikachu-Rock-Star", + "baseSpecies": "Pikachu", + "forme": "Rock-Star", + "formeLetter": "R", + "types": [ + "Electric" + ], + "gender": "F", + "baseStats": { + "hp": 35, + "atk": 55, + "def": 40, + "spa": 50, + "spd": 50, + "spe": 90 + }, + "abilities": { + "0": "Lightning Rod" + }, + "heightm": 0.4, + "weightkg": 6, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "pikachubelle": { + "num": 25, + "species": "Pikachu-Belle", + "baseSpecies": "Pikachu", + "forme": "Belle", + "formeLetter": "B", + "types": [ + "Electric" + ], + "gender": "F", + "baseStats": { + "hp": 35, + "atk": 55, + "def": 40, + "spa": 50, + "spd": 50, + "spe": 90 + }, + "abilities": { + "0": "Lightning Rod" + }, + "heightm": 0.4, + "weightkg": 6, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "pikachupopstar": { + "num": 25, + "species": "Pikachu-Pop-Star", + "baseSpecies": "Pikachu", + "forme": "Pop-Star", + "formeLetter": "P", + "types": [ + "Electric" + ], + "gender": "F", + "baseStats": { + "hp": 35, + "atk": 55, + "def": 40, + "spa": 50, + "spd": 50, + "spe": 90 + }, + "abilities": { + "0": "Lightning Rod" + }, + "heightm": 0.4, + "weightkg": 6, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "pikachuphd": { + "num": 25, + "species": "Pikachu-PhD", + "baseSpecies": "Pikachu", + "forme": "PhD", + "formeLetter": "D", + "types": [ + "Electric" + ], + "gender": "F", + "baseStats": { + "hp": 35, + "atk": 55, + "def": 40, + "spa": 50, + "spd": 50, + "spe": 90 + }, + "abilities": { + "0": "Lightning Rod" + }, + "heightm": 0.4, + "weightkg": 6, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "pikachulibre": { + "num": 25, + "species": "Pikachu-Libre", + "baseSpecies": "Pikachu", + "forme": "Libre", + "formeLetter": "L", + "types": [ + "Electric" + ], + "gender": "F", + "baseStats": { + "hp": 35, + "atk": 55, + "def": 40, + "spa": 50, + "spd": 50, + "spe": 90 + }, + "abilities": { + "0": "Lightning Rod" + }, + "heightm": 0.4, + "weightkg": 6, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "raichu": { + "num": 26, + "species": "Raichu", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 60, + "atk": 90, + "def": 55, + "spa": 90, + "spd": 80, + "spe": 110 + }, + "abilities": { + "0": "Static", + "H": "Lightning Rod" + }, + "heightm": 0.8, + "weightkg": 30, + "color": "Yellow", + "prevo": "pikachu", + "evoLevel": 1, + "eggGroups": [ + "Field", + "Fairy" + ] + }, + "sandshrew": { + "num": 27, + "species": "Sandshrew", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 50, + "atk": 75, + "def": 85, + "spa": 20, + "spd": 30, + "spe": 40 + }, + "abilities": { + "0": "Sand Veil", + "H": "Sand Rush" + }, + "heightm": 0.6, + "weightkg": 12, + "color": "Yellow", + "evos": [ + "sandslash" + ], + "eggGroups": [ + "Field" + ] + }, + "sandslash": { + "num": 28, + "species": "Sandslash", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 75, + "atk": 100, + "def": 110, + "spa": 45, + "spd": 55, + "spe": 65 + }, + "abilities": { + "0": "Sand Veil", + "H": "Sand Rush" + }, + "heightm": 1, + "weightkg": 29.5, + "color": "Yellow", + "prevo": "sandshrew", + "evoLevel": 22, + "eggGroups": [ + "Field" + ] + }, + "nidoranf": { + "num": 29, + "species": "Nidoran-F", + "types": [ + "Poison" + ], + "gender": "F", + "baseStats": { + "hp": 55, + "atk": 47, + "def": 52, + "spa": 40, + "spd": 40, + "spe": 41 + }, + "abilities": { + "0": "Poison Point", + "1": "Rivalry", + "H": "Hustle" + }, + "heightm": 0.4, + "weightkg": 7, + "color": "Blue", + "evos": [ + "nidorina" + ], + "eggGroups": [ + "Monster", + "Field" + ] + }, + "nidorina": { + "num": 30, + "species": "Nidorina", + "types": [ + "Poison" + ], + "gender": "F", + "baseStats": { + "hp": 70, + "atk": 62, + "def": 67, + "spa": 55, + "spd": 55, + "spe": 56 + }, + "abilities": { + "0": "Poison Point", + "1": "Rivalry", + "H": "Hustle" + }, + "heightm": 0.8, + "weightkg": 20, + "color": "Blue", + "prevo": "nidoranf", + "evos": [ + "nidoqueen" + ], + "evoLevel": 16, + "eggGroups": [ + "Undiscovered" + ] + }, + "nidoqueen": { + "num": 31, + "species": "Nidoqueen", + "types": [ + "Poison", + "Ground" + ], + "gender": "F", + "baseStats": { + "hp": 90, + "atk": 92, + "def": 87, + "spa": 75, + "spd": 85, + "spe": 76 + }, + "abilities": { + "0": "Poison Point", + "1": "Rivalry", + "H": "Sheer Force" + }, + "heightm": 1.3, + "weightkg": 60, + "color": "Blue", + "prevo": "nidorina", + "evoLevel": 16, + "eggGroups": [ + "Undiscovered" + ] + }, + "nidoranm": { + "num": 32, + "species": "Nidoran-M", + "types": [ + "Poison" + ], + "gender": "M", + "baseStats": { + "hp": 46, + "atk": 57, + "def": 40, + "spa": 40, + "spd": 40, + "spe": 50 + }, + "abilities": { + "0": "Poison Point", + "1": "Rivalry", + "H": "Hustle" + }, + "heightm": 0.5, + "weightkg": 9, + "color": "Purple", + "evos": [ + "nidorino" + ], + "eggGroups": [ + "Monster", + "Field" + ] + }, + "nidorino": { + "num": 33, + "species": "Nidorino", + "types": [ + "Poison" + ], + "gender": "M", + "baseStats": { + "hp": 61, + "atk": 72, + "def": 57, + "spa": 55, + "spd": 55, + "spe": 65 + }, + "abilities": { + "0": "Poison Point", + "1": "Rivalry", + "H": "Hustle" + }, + "heightm": 0.9, + "weightkg": 19.5, + "color": "Purple", + "prevo": "nidoranm", + "evos": [ + "nidoking" + ], + "evoLevel": 16, + "eggGroups": [ + "Monster", + "Field" + ] + }, + "nidoking": { + "num": 34, + "species": "Nidoking", + "types": [ + "Poison", + "Ground" + ], + "gender": "M", + "baseStats": { + "hp": 81, + "atk": 102, + "def": 77, + "spa": 85, + "spd": 75, + "spe": 85 + }, + "abilities": { + "0": "Poison Point", + "1": "Rivalry", + "H": "Sheer Force" + }, + "heightm": 1.4, + "weightkg": 62, + "color": "Purple", + "prevo": "nidorino", + "evoLevel": 16, + "eggGroups": [ + "Monster", + "Field" + ] + }, + "clefairy": { + "num": 35, + "species": "Clefairy", + "types": [ + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 70, + "atk": 45, + "def": 48, + "spa": 60, + "spd": 65, + "spe": 35 + }, + "abilities": { + "0": "Cute Charm", + "1": "Magic Guard", + "H": "Friend Guard" + }, + "heightm": 0.6, + "weightkg": 7.5, + "color": "Pink", + "prevo": "cleffa", + "evos": [ + "clefable" + ], + "evoLevel": 1, + "eggGroups": [ + "Fairy" + ] + }, + "clefable": { + "num": 36, + "species": "Clefable", + "types": [ + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 95, + "atk": 70, + "def": 73, + "spa": 95, + "spd": 90, + "spe": 60 + }, + "abilities": { + "0": "Cute Charm", + "1": "Magic Guard", + "H": "Unaware" + }, + "heightm": 1.3, + "weightkg": 40, + "color": "Pink", + "prevo": "clefairy", + "evoLevel": 1, + "eggGroups": [ + "Fairy" + ] + }, + "vulpix": { + "num": 37, + "species": "Vulpix", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 38, + "atk": 41, + "def": 40, + "spa": 50, + "spd": 65, + "spe": 65 + }, + "abilities": { + "0": "Flash Fire", + "H": "Drought" + }, + "heightm": 0.6, + "weightkg": 9.9, + "color": "Brown", + "evos": [ + "ninetales" + ], + "eggGroups": [ + "Field" + ] + }, + "ninetales": { + "num": 38, + "species": "Ninetales", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 73, + "atk": 76, + "def": 75, + "spa": 81, + "spd": 100, + "spe": 100 + }, + "abilities": { + "0": "Flash Fire", + "H": "Drought" + }, + "heightm": 1.1, + "weightkg": 19.9, + "color": "Yellow", + "prevo": "vulpix", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "jigglypuff": { + "num": 39, + "species": "Jigglypuff", + "types": [ + "Normal", + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 115, + "atk": 45, + "def": 20, + "spa": 45, + "spd": 25, + "spe": 20 + }, + "abilities": { + "0": "Cute Charm", + "1": "Competitive", + "H": "Friend Guard" + }, + "heightm": 0.5, + "weightkg": 5.5, + "color": "Pink", + "prevo": "igglybuff", + "evos": [ + "wigglytuff" + ], + "evoLevel": 1, + "eggGroups": [ + "Fairy" + ] + }, + "wigglytuff": { + "num": 40, + "species": "Wigglytuff", + "types": [ + "Normal", + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 140, + "atk": 70, + "def": 45, + "spa": 85, + "spd": 50, + "spe": 45 + }, + "abilities": { + "0": "Cute Charm", + "1": "Competitive", + "H": "Frisk" + }, + "heightm": 1, + "weightkg": 12, + "color": "Pink", + "prevo": "jigglypuff", + "evoLevel": 1, + "eggGroups": [ + "Fairy" + ] + }, + "zubat": { + "num": 41, + "species": "Zubat", + "types": [ + "Poison", + "Flying" + ], + "baseStats": { + "hp": 40, + "atk": 45, + "def": 35, + "spa": 30, + "spd": 40, + "spe": 55 + }, + "abilities": { + "0": "Inner Focus", + "H": "Infiltrator" + }, + "heightm": 0.8, + "weightkg": 7.5, + "color": "Purple", + "evos": [ + "golbat" + ], + "eggGroups": [ + "Flying" + ] + }, + "golbat": { + "num": 42, + "species": "Golbat", + "types": [ + "Poison", + "Flying" + ], + "baseStats": { + "hp": 75, + "atk": 80, + "def": 70, + "spa": 65, + "spd": 75, + "spe": 90 + }, + "abilities": { + "0": "Inner Focus", + "H": "Infiltrator" + }, + "heightm": 1.6, + "weightkg": 55, + "color": "Purple", + "prevo": "zubat", + "evos": [ + "crobat" + ], + "evoLevel": 22, + "eggGroups": [ + "Flying" + ] + }, + "oddish": { + "num": 43, + "species": "Oddish", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 45, + "atk": 50, + "def": 55, + "spa": 75, + "spd": 65, + "spe": 30 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Run Away" + }, + "heightm": 0.5, + "weightkg": 5.4, + "color": "Blue", + "evos": [ + "gloom" + ], + "eggGroups": [ + "Grass" + ] + }, + "gloom": { + "num": 44, + "species": "Gloom", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 65, + "def": 70, + "spa": 85, + "spd": 75, + "spe": 40 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Stench" + }, + "heightm": 0.8, + "weightkg": 8.6, + "color": "Blue", + "prevo": "oddish", + "evos": [ + "vileplume", + "bellossom" + ], + "evoLevel": 21, + "eggGroups": [ + "Grass" + ] + }, + "vileplume": { + "num": 45, + "species": "Vileplume", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 75, + "atk": 80, + "def": 85, + "spa": 110, + "spd": 90, + "spe": 50 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Effect Spore" + }, + "heightm": 1.2, + "weightkg": 18.6, + "color": "Red", + "prevo": "gloom", + "evoLevel": 21, + "eggGroups": [ + "Grass" + ] + }, + "paras": { + "num": 46, + "species": "Paras", + "types": [ + "Bug", + "Grass" + ], + "baseStats": { + "hp": 35, + "atk": 70, + "def": 55, + "spa": 45, + "spd": 55, + "spe": 25 + }, + "abilities": { + "0": "Effect Spore", + "1": "Dry Skin", + "H": "Damp" + }, + "heightm": 0.3, + "weightkg": 5.4, + "color": "Red", + "evos": [ + "parasect" + ], + "eggGroups": [ + "Bug", + "Grass" + ] + }, + "parasect": { + "num": 47, + "species": "Parasect", + "types": [ + "Bug", + "Grass" + ], + "baseStats": { + "hp": 60, + "atk": 95, + "def": 80, + "spa": 60, + "spd": 80, + "spe": 30 + }, + "abilities": { + "0": "Effect Spore", + "1": "Dry Skin", + "H": "Damp" + }, + "heightm": 1, + "weightkg": 29.5, + "color": "Red", + "prevo": "paras", + "evoLevel": 24, + "eggGroups": [ + "Bug", + "Grass" + ] + }, + "venonat": { + "num": 48, + "species": "Venonat", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 55, + "def": 50, + "spa": 40, + "spd": 55, + "spe": 45 + }, + "abilities": { + "0": "Compound Eyes", + "1": "Tinted Lens", + "H": "Run Away" + }, + "heightm": 1, + "weightkg": 30, + "color": "Purple", + "evos": [ + "venomoth" + ], + "eggGroups": [ + "Bug" + ] + }, + "venomoth": { + "num": 49, + "species": "Venomoth", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 70, + "atk": 65, + "def": 60, + "spa": 90, + "spd": 75, + "spe": 90 + }, + "abilities": { + "0": "Shield Dust", + "1": "Tinted Lens", + "H": "Wonder Skin" + }, + "heightm": 1.5, + "weightkg": 12.5, + "color": "Purple", + "prevo": "venonat", + "evoLevel": 31, + "eggGroups": [ + "Bug" + ] + }, + "diglett": { + "num": 50, + "species": "Diglett", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 10, + "atk": 55, + "def": 25, + "spa": 35, + "spd": 45, + "spe": 95 + }, + "abilities": { + "0": "Sand Veil", + "1": "Arena Trap", + "H": "Sand Force" + }, + "heightm": 0.2, + "weightkg": 0.8, + "color": "Brown", + "evos": [ + "dugtrio" + ], + "eggGroups": [ + "Field" + ] + }, + "dugtrio": { + "num": 51, + "species": "Dugtrio", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 35, + "atk": 80, + "def": 50, + "spa": 50, + "spd": 70, + "spe": 120 + }, + "abilities": { + "0": "Sand Veil", + "1": "Arena Trap", + "H": "Sand Force" + }, + "heightm": 0.7, + "weightkg": 33.3, + "color": "Brown", + "prevo": "diglett", + "evoLevel": 26, + "eggGroups": [ + "Field" + ] + }, + "meowth": { + "num": 52, + "species": "Meowth", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 40, + "atk": 45, + "def": 35, + "spa": 40, + "spd": 40, + "spe": 90 + }, + "abilities": { + "0": "Pickup", + "1": "Technician", + "H": "Unnerve" + }, + "heightm": 0.4, + "weightkg": 4.2, + "color": "Yellow", + "evos": [ + "persian" + ], + "eggGroups": [ + "Field" + ] + }, + "persian": { + "num": 53, + "species": "Persian", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 65, + "atk": 70, + "def": 60, + "spa": 65, + "spd": 65, + "spe": 115 + }, + "abilities": { + "0": "Limber", + "1": "Technician", + "H": "Unnerve" + }, + "heightm": 1, + "weightkg": 32, + "color": "Yellow", + "prevo": "meowth", + "evoLevel": 28, + "eggGroups": [ + "Field" + ] + }, + "psyduck": { + "num": 54, + "species": "Psyduck", + "types": [ + "Water" + ], + "baseStats": { + "hp": 50, + "atk": 52, + "def": 48, + "spa": 65, + "spd": 50, + "spe": 55 + }, + "abilities": { + "0": "Damp", + "1": "Cloud Nine", + "H": "Swift Swim" + }, + "heightm": 0.8, + "weightkg": 19.6, + "color": "Yellow", + "evos": [ + "golduck" + ], + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "golduck": { + "num": 55, + "species": "Golduck", + "types": [ + "Water" + ], + "baseStats": { + "hp": 80, + "atk": 82, + "def": 78, + "spa": 95, + "spd": 80, + "spe": 85 + }, + "abilities": { + "0": "Damp", + "1": "Cloud Nine", + "H": "Swift Swim" + }, + "heightm": 1.7, + "weightkg": 76.6, + "color": "Blue", + "prevo": "psyduck", + "evoLevel": 33, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "mankey": { + "num": 56, + "species": "Mankey", + "types": [ + "Fighting" + ], + "baseStats": { + "hp": 40, + "atk": 80, + "def": 35, + "spa": 35, + "spd": 45, + "spe": 70 + }, + "abilities": { + "0": "Vital Spirit", + "1": "Anger Point", + "H": "Defiant" + }, + "heightm": 0.5, + "weightkg": 28, + "color": "Brown", + "evos": [ + "primeape" + ], + "eggGroups": [ + "Field" + ] + }, + "primeape": { + "num": 57, + "species": "Primeape", + "types": [ + "Fighting" + ], + "baseStats": { + "hp": 65, + "atk": 105, + "def": 60, + "spa": 60, + "spd": 70, + "spe": 95 + }, + "abilities": { + "0": "Vital Spirit", + "1": "Anger Point", + "H": "Defiant" + }, + "heightm": 1, + "weightkg": 32, + "color": "Brown", + "prevo": "mankey", + "evoLevel": 28, + "eggGroups": [ + "Field" + ] + }, + "growlithe": { + "num": 58, + "species": "Growlithe", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 55, + "atk": 70, + "def": 45, + "spa": 70, + "spd": 50, + "spe": 60 + }, + "abilities": { + "0": "Intimidate", + "1": "Flash Fire", + "H": "Justified" + }, + "heightm": 0.7, + "weightkg": 19, + "color": "Brown", + "evos": [ + "arcanine" + ], + "eggGroups": [ + "Field" + ] + }, + "arcanine": { + "num": 59, + "species": "Arcanine", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 90, + "atk": 110, + "def": 80, + "spa": 100, + "spd": 80, + "spe": 95 + }, + "abilities": { + "0": "Intimidate", + "1": "Flash Fire", + "H": "Justified" + }, + "heightm": 1.9, + "weightkg": 155, + "color": "Brown", + "prevo": "growlithe", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "poliwag": { + "num": 60, + "species": "Poliwag", + "types": [ + "Water" + ], + "baseStats": { + "hp": 40, + "atk": 50, + "def": 40, + "spa": 40, + "spd": 40, + "spe": 90 + }, + "abilities": { + "0": "Water Absorb", + "1": "Damp", + "H": "Swift Swim" + }, + "heightm": 0.6, + "weightkg": 12.4, + "color": "Blue", + "evos": [ + "poliwhirl" + ], + "eggGroups": [ + "Water 1" + ] + }, + "poliwhirl": { + "num": 61, + "species": "Poliwhirl", + "types": [ + "Water" + ], + "baseStats": { + "hp": 65, + "atk": 65, + "def": 65, + "spa": 50, + "spd": 50, + "spe": 90 + }, + "abilities": { + "0": "Water Absorb", + "1": "Damp", + "H": "Swift Swim" + }, + "heightm": 1, + "weightkg": 20, + "color": "Blue", + "prevo": "poliwag", + "evos": [ + "poliwrath", + "politoed" + ], + "evoLevel": 25, + "eggGroups": [ + "Water 1" + ] + }, + "poliwrath": { + "num": 62, + "species": "Poliwrath", + "types": [ + "Water", + "Fighting" + ], + "baseStats": { + "hp": 90, + "atk": 95, + "def": 95, + "spa": 70, + "spd": 90, + "spe": 70 + }, + "abilities": { + "0": "Water Absorb", + "1": "Damp", + "H": "Swift Swim" + }, + "heightm": 1.3, + "weightkg": 54, + "color": "Blue", + "prevo": "poliwhirl", + "evoLevel": 25, + "eggGroups": [ + "Water 1" + ] + }, + "abra": { + "num": 63, + "species": "Abra", + "types": [ + "Psychic" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 25, + "atk": 20, + "def": 15, + "spa": 105, + "spd": 55, + "spe": 90 + }, + "abilities": { + "0": "Synchronize", + "1": "Inner Focus", + "H": "Magic Guard" + }, + "heightm": 0.9, + "weightkg": 19.5, + "color": "Brown", + "evos": [ + "kadabra" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "kadabra": { + "num": 64, + "species": "Kadabra", + "types": [ + "Psychic" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 40, + "atk": 35, + "def": 30, + "spa": 120, + "spd": 70, + "spe": 105 + }, + "abilities": { + "0": "Synchronize", + "1": "Inner Focus", + "H": "Magic Guard" + }, + "heightm": 1.3, + "weightkg": 56.5, + "color": "Brown", + "prevo": "abra", + "evos": [ + "alakazam" + ], + "evoLevel": 16, + "eggGroups": [ + "Human-Like" + ] + }, + "alakazam": { + "num": 65, + "species": "Alakazam", + "types": [ + "Psychic" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 55, + "atk": 50, + "def": 45, + "spa": 135, + "spd": 95, + "spe": 120 + }, + "abilities": { + "0": "Synchronize", + "1": "Inner Focus", + "H": "Magic Guard" + }, + "heightm": 1.5, + "weightkg": 48, + "color": "Brown", + "prevo": "kadabra", + "evoLevel": 16, + "eggGroups": [ + "Human-Like" + ], + "otherFormes": [ + "alakazammega" + ] + }, + "alakazammega": { + "num": 65, + "species": "Alakazam-Mega", + "baseSpecies": "Alakazam", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Psychic" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 55, + "atk": 50, + "def": 65, + "spa": 175, + "spd": 95, + "spe": 150 + }, + "abilities": { + "0": "Trace" + }, + "heightm": 1.2, + "weightkg": 48, + "color": "Brown", + "eggGroups": [ + "Human-Like" + ] + }, + "machop": { + "num": 66, + "species": "Machop", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 70, + "atk": 80, + "def": 50, + "spa": 35, + "spd": 35, + "spe": 35 + }, + "abilities": { + "0": "Guts", + "1": "No Guard", + "H": "Steadfast" + }, + "heightm": 0.8, + "weightkg": 19.5, + "color": "Gray", + "evos": [ + "machoke" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "machoke": { + "num": 67, + "species": "Machoke", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 80, + "atk": 100, + "def": 70, + "spa": 50, + "spd": 60, + "spe": 45 + }, + "abilities": { + "0": "Guts", + "1": "No Guard", + "H": "Steadfast" + }, + "heightm": 1.5, + "weightkg": 70.5, + "color": "Gray", + "prevo": "machop", + "evos": [ + "machamp" + ], + "evoLevel": 28, + "eggGroups": [ + "Human-Like" + ] + }, + "machamp": { + "num": 68, + "species": "Machamp", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 90, + "atk": 130, + "def": 80, + "spa": 65, + "spd": 85, + "spe": 55 + }, + "abilities": { + "0": "Guts", + "1": "No Guard", + "H": "Steadfast" + }, + "heightm": 1.6, + "weightkg": 130, + "color": "Gray", + "prevo": "machoke", + "evoLevel": 28, + "eggGroups": [ + "Human-Like" + ] + }, + "bellsprout": { + "num": 69, + "species": "Bellsprout", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 50, + "atk": 75, + "def": 35, + "spa": 70, + "spd": 30, + "spe": 40 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Gluttony" + }, + "heightm": 0.7, + "weightkg": 4, + "color": "Green", + "evos": [ + "weepinbell" + ], + "eggGroups": [ + "Grass" + ] + }, + "weepinbell": { + "num": 70, + "species": "Weepinbell", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 65, + "atk": 90, + "def": 50, + "spa": 85, + "spd": 45, + "spe": 55 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Gluttony" + }, + "heightm": 1, + "weightkg": 6.4, + "color": "Green", + "prevo": "bellsprout", + "evos": [ + "victreebel" + ], + "evoLevel": 21, + "eggGroups": [ + "Grass" + ] + }, + "victreebel": { + "num": 71, + "species": "Victreebel", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 80, + "atk": 105, + "def": 65, + "spa": 100, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Gluttony" + }, + "heightm": 1.7, + "weightkg": 15.5, + "color": "Green", + "prevo": "weepinbell", + "evoLevel": 21, + "eggGroups": [ + "Grass" + ] + }, + "tentacool": { + "num": 72, + "species": "Tentacool", + "types": [ + "Water", + "Poison" + ], + "baseStats": { + "hp": 40, + "atk": 40, + "def": 35, + "spa": 50, + "spd": 100, + "spe": 70 + }, + "abilities": { + "0": "Clear Body", + "1": "Liquid Ooze", + "H": "Rain Dish" + }, + "heightm": 0.9, + "weightkg": 45.5, + "color": "Blue", + "evos": [ + "tentacruel" + ], + "eggGroups": [ + "Water 3" + ] + }, + "tentacruel": { + "num": 73, + "species": "Tentacruel", + "types": [ + "Water", + "Poison" + ], + "baseStats": { + "hp": 80, + "atk": 70, + "def": 65, + "spa": 80, + "spd": 120, + "spe": 100 + }, + "abilities": { + "0": "Clear Body", + "1": "Liquid Ooze", + "H": "Rain Dish" + }, + "heightm": 1.6, + "weightkg": 55, + "color": "Blue", + "prevo": "tentacool", + "evoLevel": 30, + "eggGroups": [ + "Water 3" + ] + }, + "geodude": { + "num": 74, + "species": "Geodude", + "types": [ + "Rock", + "Ground" + ], + "baseStats": { + "hp": 40, + "atk": 80, + "def": 100, + "spa": 30, + "spd": 30, + "spe": 20 + }, + "abilities": { + "0": "Rock Head", + "1": "Sturdy", + "H": "Sand Veil" + }, + "heightm": 0.4, + "weightkg": 20, + "color": "Brown", + "evos": [ + "graveler" + ], + "eggGroups": [ + "Mineral" + ] + }, + "graveler": { + "num": 75, + "species": "Graveler", + "types": [ + "Rock", + "Ground" + ], + "baseStats": { + "hp": 55, + "atk": 95, + "def": 115, + "spa": 45, + "spd": 45, + "spe": 35 + }, + "abilities": { + "0": "Rock Head", + "1": "Sturdy", + "H": "Sand Veil" + }, + "heightm": 1, + "weightkg": 105, + "color": "Brown", + "prevo": "geodude", + "evos": [ + "golem" + ], + "evoLevel": 25, + "eggGroups": [ + "Mineral" + ] + }, + "golem": { + "num": 76, + "species": "Golem", + "types": [ + "Rock", + "Ground" + ], + "baseStats": { + "hp": 80, + "atk": 120, + "def": 130, + "spa": 55, + "spd": 65, + "spe": 45 + }, + "abilities": { + "0": "Rock Head", + "1": "Sturdy", + "H": "Sand Veil" + }, + "heightm": 1.4, + "weightkg": 300, + "color": "Brown", + "prevo": "graveler", + "evoLevel": 25, + "eggGroups": [ + "Mineral" + ] + }, + "ponyta": { + "num": 77, + "species": "Ponyta", + "types": [ + "Fire" + ], + "baseStats": { + "hp": 50, + "atk": 85, + "def": 55, + "spa": 65, + "spd": 65, + "spe": 90 + }, + "abilities": { + "0": "Run Away", + "1": "Flash Fire", + "H": "Flame Body" + }, + "heightm": 1, + "weightkg": 30, + "color": "Yellow", + "evos": [ + "rapidash" + ], + "eggGroups": [ + "Field" + ] + }, + "rapidash": { + "num": 78, + "species": "Rapidash", + "types": [ + "Fire" + ], + "baseStats": { + "hp": 65, + "atk": 100, + "def": 70, + "spa": 80, + "spd": 80, + "spe": 105 + }, + "abilities": { + "0": "Run Away", + "1": "Flash Fire", + "H": "Flame Body" + }, + "heightm": 1.7, + "weightkg": 95, + "color": "Yellow", + "prevo": "ponyta", + "evoLevel": 40, + "eggGroups": [ + "Field" + ] + }, + "slowpoke": { + "num": 79, + "species": "Slowpoke", + "types": [ + "Water", + "Psychic" + ], + "baseStats": { + "hp": 90, + "atk": 65, + "def": 65, + "spa": 40, + "spd": 40, + "spe": 15 + }, + "abilities": { + "0": "Oblivious", + "1": "Own Tempo", + "H": "Regenerator" + }, + "heightm": 1.2, + "weightkg": 36, + "color": "Pink", + "evos": [ + "slowbro", + "slowking" + ], + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "slowbro": { + "num": 80, + "species": "Slowbro", + "types": [ + "Water", + "Psychic" + ], + "baseStats": { + "hp": 95, + "atk": 75, + "def": 110, + "spa": 100, + "spd": 80, + "spe": 30 + }, + "abilities": { + "0": "Oblivious", + "1": "Own Tempo", + "H": "Regenerator" + }, + "heightm": 1.6, + "weightkg": 78.5, + "color": "Pink", + "prevo": "slowpoke", + "evoLevel": 37, + "eggGroups": [ + "Monster", + "Water 1" + ], + "otherFormes": [ + "slowbromega" + ] + }, + "slowbromega": { + "num": 80, + "species": "Slowbro-Mega", + "baseSpecies": "Slowbro", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Water", + "Psychic" + ], + "baseStats": { + "hp": 95, + "atk": 75, + "def": 180, + "spa": 130, + "spd": 80, + "spe": 30 + }, + "abilities": { + "0": "Shell Armor" + }, + "heightm": 2, + "weightkg": 120, + "color": "Pink", + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "magnemite": { + "num": 81, + "species": "Magnemite", + "types": [ + "Electric", + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 25, + "atk": 35, + "def": 70, + "spa": 95, + "spd": 55, + "spe": 45 + }, + "abilities": { + "0": "Magnet Pull", + "1": "Sturdy", + "H": "Analytic" + }, + "heightm": 0.3, + "weightkg": 6, + "color": "Gray", + "evos": [ + "magneton" + ], + "eggGroups": [ + "Mineral" + ] + }, + "magneton": { + "num": 82, + "species": "Magneton", + "types": [ + "Electric", + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 60, + "def": 95, + "spa": 120, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Magnet Pull", + "1": "Sturdy", + "H": "Analytic" + }, + "heightm": 1, + "weightkg": 60, + "color": "Gray", + "prevo": "magnemite", + "evos": [ + "magnezone" + ], + "evoLevel": 30, + "eggGroups": [ + "Mineral" + ] + }, + "farfetchd": { + "num": 83, + "species": "Farfetch'd", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 52, + "atk": 65, + "def": 55, + "spa": 58, + "spd": 62, + "spe": 60 + }, + "abilities": { + "0": "Keen Eye", + "1": "Inner Focus", + "H": "Defiant" + }, + "heightm": 0.8, + "weightkg": 15, + "color": "Brown", + "eggGroups": [ + "Flying", + "Field" + ] + }, + "doduo": { + "num": 84, + "species": "Doduo", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 35, + "atk": 85, + "def": 45, + "spa": 35, + "spd": 35, + "spe": 75 + }, + "abilities": { + "0": "Run Away", + "1": "Early Bird", + "H": "Tangled Feet" + }, + "heightm": 1.4, + "weightkg": 39.2, + "color": "Brown", + "evos": [ + "dodrio" + ], + "eggGroups": [ + "Flying" + ] + }, + "dodrio": { + "num": 85, + "species": "Dodrio", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 60, + "atk": 110, + "def": 70, + "spa": 60, + "spd": 60, + "spe": 100 + }, + "abilities": { + "0": "Run Away", + "1": "Early Bird", + "H": "Tangled Feet" + }, + "heightm": 1.8, + "weightkg": 85.2, + "color": "Brown", + "prevo": "doduo", + "evoLevel": 31, + "eggGroups": [ + "Flying" + ] + }, + "seel": { + "num": 86, + "species": "Seel", + "types": [ + "Water" + ], + "baseStats": { + "hp": 65, + "atk": 45, + "def": 55, + "spa": 45, + "spd": 70, + "spe": 45 + }, + "abilities": { + "0": "Thick Fat", + "1": "Hydration", + "H": "Ice Body" + }, + "heightm": 1.1, + "weightkg": 90, + "color": "White", + "evos": [ + "dewgong" + ], + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "dewgong": { + "num": 87, + "species": "Dewgong", + "types": [ + "Water", + "Ice" + ], + "baseStats": { + "hp": 90, + "atk": 70, + "def": 80, + "spa": 70, + "spd": 95, + "spe": 70 + }, + "abilities": { + "0": "Thick Fat", + "1": "Hydration", + "H": "Ice Body" + }, + "heightm": 1.7, + "weightkg": 120, + "color": "White", + "prevo": "seel", + "evoLevel": 34, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "grimer": { + "num": 88, + "species": "Grimer", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 80, + "atk": 80, + "def": 50, + "spa": 40, + "spd": 50, + "spe": 25 + }, + "abilities": { + "0": "Stench", + "1": "Sticky Hold", + "H": "Poison Touch" + }, + "heightm": 0.9, + "weightkg": 30, + "color": "Purple", + "evos": [ + "muk" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "muk": { + "num": 89, + "species": "Muk", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 105, + "atk": 105, + "def": 75, + "spa": 65, + "spd": 100, + "spe": 50 + }, + "abilities": { + "0": "Stench", + "1": "Sticky Hold", + "H": "Poison Touch" + }, + "heightm": 1.2, + "weightkg": 30, + "color": "Purple", + "prevo": "grimer", + "evoLevel": 38, + "eggGroups": [ + "Amorphous" + ] + }, + "shellder": { + "num": 90, + "species": "Shellder", + "types": [ + "Water" + ], + "baseStats": { + "hp": 30, + "atk": 65, + "def": 100, + "spa": 45, + "spd": 25, + "spe": 40 + }, + "abilities": { + "0": "Shell Armor", + "1": "Skill Link", + "H": "Overcoat" + }, + "heightm": 0.3, + "weightkg": 4, + "color": "Purple", + "evos": [ + "cloyster" + ], + "eggGroups": [ + "Water 3" + ] + }, + "cloyster": { + "num": 91, + "species": "Cloyster", + "types": [ + "Water", + "Ice" + ], + "baseStats": { + "hp": 50, + "atk": 95, + "def": 180, + "spa": 85, + "spd": 45, + "spe": 70 + }, + "abilities": { + "0": "Shell Armor", + "1": "Skill Link", + "H": "Overcoat" + }, + "heightm": 1.5, + "weightkg": 132.5, + "color": "Purple", + "prevo": "shellder", + "evoLevel": 1, + "eggGroups": [ + "Water 3" + ] + }, + "gastly": { + "num": 92, + "species": "Gastly", + "types": [ + "Ghost", + "Poison" + ], + "baseStats": { + "hp": 30, + "atk": 35, + "def": 30, + "spa": 100, + "spd": 35, + "spe": 80 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.3, + "weightkg": 0.1, + "color": "Purple", + "evos": [ + "haunter" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "haunter": { + "num": 93, + "species": "Haunter", + "types": [ + "Ghost", + "Poison" + ], + "baseStats": { + "hp": 45, + "atk": 50, + "def": 45, + "spa": 115, + "spd": 55, + "spe": 95 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.6, + "weightkg": 0.1, + "color": "Purple", + "prevo": "gastly", + "evos": [ + "gengar" + ], + "evoLevel": 25, + "eggGroups": [ + "Amorphous" + ] + }, + "gengar": { + "num": 94, + "species": "Gengar", + "types": [ + "Ghost", + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 65, + "def": 60, + "spa": 130, + "spd": 75, + "spe": 110 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.5, + "weightkg": 40.5, + "color": "Purple", + "prevo": "haunter", + "evoLevel": 25, + "eggGroups": [ + "Amorphous" + ], + "otherFormes": [ + "gengarmega" + ] + }, + "gengarmega": { + "num": 94, + "species": "Gengar-Mega", + "baseSpecies": "Gengar", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Ghost", + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 65, + "def": 80, + "spa": 170, + "spd": 95, + "spe": 130 + }, + "abilities": { + "0": "Shadow Tag" + }, + "heightm": 1.4, + "weightkg": 40.5, + "color": "Purple", + "eggGroups": [ + "Amorphous" + ] + }, + "onix": { + "num": 95, + "species": "Onix", + "types": [ + "Rock", + "Ground" + ], + "baseStats": { + "hp": 35, + "atk": 45, + "def": 160, + "spa": 30, + "spd": 45, + "spe": 70 + }, + "abilities": { + "0": "Rock Head", + "1": "Sturdy", + "H": "Weak Armor" + }, + "heightm": 8.8, + "weightkg": 210, + "color": "Gray", + "evos": [ + "steelix" + ], + "eggGroups": [ + "Mineral" + ] + }, + "drowzee": { + "num": 96, + "species": "Drowzee", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 60, + "atk": 48, + "def": 45, + "spa": 43, + "spd": 90, + "spe": 42 + }, + "abilities": { + "0": "Insomnia", + "1": "Forewarn", + "H": "Inner Focus" + }, + "heightm": 1, + "weightkg": 32.4, + "color": "Yellow", + "evos": [ + "hypno" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "hypno": { + "num": 97, + "species": "Hypno", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 85, + "atk": 73, + "def": 70, + "spa": 73, + "spd": 115, + "spe": 67 + }, + "abilities": { + "0": "Insomnia", + "1": "Forewarn", + "H": "Inner Focus" + }, + "heightm": 1.6, + "weightkg": 75.6, + "color": "Yellow", + "prevo": "drowzee", + "evoLevel": 26, + "eggGroups": [ + "Human-Like" + ] + }, + "krabby": { + "num": 98, + "species": "Krabby", + "types": [ + "Water" + ], + "baseStats": { + "hp": 30, + "atk": 105, + "def": 90, + "spa": 25, + "spd": 25, + "spe": 50 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Shell Armor", + "H": "Sheer Force" + }, + "heightm": 0.4, + "weightkg": 6.5, + "color": "Red", + "evos": [ + "kingler" + ], + "eggGroups": [ + "Water 3" + ] + }, + "kingler": { + "num": 99, + "species": "Kingler", + "types": [ + "Water" + ], + "baseStats": { + "hp": 55, + "atk": 130, + "def": 115, + "spa": 50, + "spd": 50, + "spe": 75 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Shell Armor", + "H": "Sheer Force" + }, + "heightm": 1.3, + "weightkg": 60, + "color": "Red", + "prevo": "krabby", + "evoLevel": 28, + "eggGroups": [ + "Water 3" + ] + }, + "voltorb": { + "num": 100, + "species": "Voltorb", + "types": [ + "Electric" + ], + "gender": "N", + "baseStats": { + "hp": 40, + "atk": 30, + "def": 50, + "spa": 55, + "spd": 55, + "spe": 100 + }, + "abilities": { + "0": "Soundproof", + "1": "Static", + "H": "Aftermath" + }, + "heightm": 0.5, + "weightkg": 10.4, + "color": "Red", + "evos": [ + "electrode" + ], + "eggGroups": [ + "Mineral" + ] + }, + "electrode": { + "num": 101, + "species": "Electrode", + "types": [ + "Electric" + ], + "gender": "N", + "baseStats": { + "hp": 60, + "atk": 50, + "def": 70, + "spa": 80, + "spd": 80, + "spe": 140 + }, + "abilities": { + "0": "Soundproof", + "1": "Static", + "H": "Aftermath" + }, + "heightm": 1.2, + "weightkg": 66.6, + "color": "Red", + "prevo": "voltorb", + "evoLevel": 30, + "eggGroups": [ + "Mineral" + ] + }, + "exeggcute": { + "num": 102, + "species": "Exeggcute", + "types": [ + "Grass", + "Psychic" + ], + "baseStats": { + "hp": 60, + "atk": 40, + "def": 80, + "spa": 60, + "spd": 45, + "spe": 40 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Harvest" + }, + "heightm": 0.4, + "weightkg": 2.5, + "color": "Pink", + "evos": [ + "exeggutor" + ], + "eggGroups": [ + "Grass" + ] + }, + "exeggutor": { + "num": 103, + "species": "Exeggutor", + "types": [ + "Grass", + "Psychic" + ], + "baseStats": { + "hp": 95, + "atk": 95, + "def": 85, + "spa": 125, + "spd": 65, + "spe": 55 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Harvest" + }, + "heightm": 2, + "weightkg": 120, + "color": "Yellow", + "prevo": "exeggcute", + "evoLevel": 1, + "eggGroups": [ + "Grass" + ] + }, + "cubone": { + "num": 104, + "species": "Cubone", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 50, + "atk": 50, + "def": 95, + "spa": 40, + "spd": 50, + "spe": 35 + }, + "abilities": { + "0": "Rock Head", + "1": "Lightning Rod", + "H": "Battle Armor" + }, + "heightm": 0.4, + "weightkg": 6.5, + "color": "Brown", + "evos": [ + "marowak" + ], + "eggGroups": [ + "Monster" + ] + }, + "marowak": { + "num": 105, + "species": "Marowak", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 60, + "atk": 80, + "def": 110, + "spa": 50, + "spd": 80, + "spe": 45 + }, + "abilities": { + "0": "Rock Head", + "1": "Lightning Rod", + "H": "Battle Armor" + }, + "heightm": 1, + "weightkg": 45, + "color": "Brown", + "prevo": "cubone", + "evoLevel": 28, + "eggGroups": [ + "Monster" + ] + }, + "hitmonlee": { + "num": 106, + "species": "Hitmonlee", + "types": [ + "Fighting" + ], + "gender": "M", + "baseStats": { + "hp": 50, + "atk": 120, + "def": 53, + "spa": 35, + "spd": 110, + "spe": 87 + }, + "abilities": { + "0": "Limber", + "1": "Reckless", + "H": "Unburden" + }, + "heightm": 1.5, + "weightkg": 49.8, + "color": "Brown", + "prevo": "tyrogue", + "evoLevel": 20, + "eggGroups": [ + "Human-Like" + ] + }, + "hitmonchan": { + "num": 107, + "species": "Hitmonchan", + "types": [ + "Fighting" + ], + "gender": "M", + "baseStats": { + "hp": 50, + "atk": 105, + "def": 79, + "spa": 35, + "spd": 110, + "spe": 76 + }, + "abilities": { + "0": "Keen Eye", + "1": "Iron Fist", + "H": "Inner Focus" + }, + "heightm": 1.4, + "weightkg": 50.2, + "color": "Brown", + "prevo": "tyrogue", + "evoLevel": 20, + "eggGroups": [ + "Human-Like" + ] + }, + "lickitung": { + "num": 108, + "species": "Lickitung", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 90, + "atk": 55, + "def": 75, + "spa": 60, + "spd": 75, + "spe": 30 + }, + "abilities": { + "0": "Own Tempo", + "1": "Oblivious", + "H": "Cloud Nine" + }, + "heightm": 1.2, + "weightkg": 65.5, + "color": "Pink", + "evos": [ + "lickilicky" + ], + "eggGroups": [ + "Monster" + ] + }, + "koffing": { + "num": 109, + "species": "Koffing", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 40, + "atk": 65, + "def": 95, + "spa": 60, + "spd": 45, + "spe": 35 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.6, + "weightkg": 1, + "color": "Purple", + "evos": [ + "weezing" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "weezing": { + "num": 110, + "species": "Weezing", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 65, + "atk": 90, + "def": 120, + "spa": 85, + "spd": 70, + "spe": 60 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.2, + "weightkg": 9.5, + "color": "Purple", + "prevo": "koffing", + "evoLevel": 35, + "eggGroups": [ + "Amorphous" + ] + }, + "rhyhorn": { + "num": 111, + "species": "Rhyhorn", + "types": [ + "Ground", + "Rock" + ], + "baseStats": { + "hp": 80, + "atk": 85, + "def": 95, + "spa": 30, + "spd": 30, + "spe": 25 + }, + "abilities": { + "0": "Lightning Rod", + "1": "Rock Head", + "H": "Reckless" + }, + "heightm": 1, + "weightkg": 115, + "color": "Gray", + "evos": [ + "rhydon" + ], + "eggGroups": [ + "Monster", + "Field" + ] + }, + "rhydon": { + "num": 112, + "species": "Rhydon", + "types": [ + "Ground", + "Rock" + ], + "baseStats": { + "hp": 105, + "atk": 130, + "def": 120, + "spa": 45, + "spd": 45, + "spe": 40 + }, + "abilities": { + "0": "Lightning Rod", + "1": "Rock Head", + "H": "Reckless" + }, + "heightm": 1.9, + "weightkg": 120, + "color": "Gray", + "prevo": "rhyhorn", + "evos": [ + "rhyperior" + ], + "evoLevel": 42, + "eggGroups": [ + "Monster", + "Field" + ] + }, + "chansey": { + "num": 113, + "species": "Chansey", + "types": [ + "Normal" + ], + "gender": "F", + "baseStats": { + "hp": 250, + "atk": 5, + "def": 5, + "spa": 35, + "spd": 105, + "spe": 50 + }, + "abilities": { + "0": "Natural Cure", + "1": "Serene Grace", + "H": "Healer" + }, + "heightm": 1.1, + "weightkg": 34.6, + "color": "Pink", + "prevo": "happiny", + "evos": [ + "blissey" + ], + "evoLevel": 1, + "eggGroups": [ + "Fairy" + ] + }, + "tangela": { + "num": 114, + "species": "Tangela", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 65, + "atk": 55, + "def": 115, + "spa": 100, + "spd": 40, + "spe": 60 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Leaf Guard", + "H": "Regenerator" + }, + "heightm": 1, + "weightkg": 35, + "color": "Blue", + "evos": [ + "tangrowth" + ], + "eggGroups": [ + "Grass" + ] + }, + "kangaskhan": { + "num": 115, + "species": "Kangaskhan", + "types": [ + "Normal" + ], + "gender": "F", + "baseStats": { + "hp": 105, + "atk": 95, + "def": 80, + "spa": 40, + "spd": 80, + "spe": 90 + }, + "abilities": { + "0": "Early Bird", + "1": "Scrappy", + "H": "Inner Focus" + }, + "heightm": 2.2, + "weightkg": 80, + "color": "Brown", + "eggGroups": [ + "Monster" + ], + "otherFormes": [ + "kangaskhanmega" + ] + }, + "kangaskhanmega": { + "num": 115, + "species": "Kangaskhan-Mega", + "baseSpecies": "Kangaskhan", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Normal" + ], + "gender": "F", + "baseStats": { + "hp": 105, + "atk": 125, + "def": 100, + "spa": 60, + "spd": 100, + "spe": 100 + }, + "abilities": { + "0": "Parental Bond" + }, + "heightm": 2.2, + "weightkg": 100, + "color": "Brown", + "eggGroups": [ + "Monster" + ] + }, + "horsea": { + "num": 116, + "species": "Horsea", + "types": [ + "Water" + ], + "baseStats": { + "hp": 30, + "atk": 40, + "def": 70, + "spa": 70, + "spd": 25, + "spe": 60 + }, + "abilities": { + "0": "Swift Swim", + "1": "Sniper", + "H": "Damp" + }, + "heightm": 0.4, + "weightkg": 8, + "color": "Blue", + "evos": [ + "seadra" + ], + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "seadra": { + "num": 117, + "species": "Seadra", + "types": [ + "Water" + ], + "baseStats": { + "hp": 55, + "atk": 65, + "def": 95, + "spa": 95, + "spd": 45, + "spe": 85 + }, + "abilities": { + "0": "Poison Point", + "1": "Sniper", + "H": "Damp" + }, + "heightm": 1.2, + "weightkg": 25, + "color": "Blue", + "prevo": "horsea", + "evos": [ + "kingdra" + ], + "evoLevel": 32, + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "goldeen": { + "num": 118, + "species": "Goldeen", + "types": [ + "Water" + ], + "baseStats": { + "hp": 45, + "atk": 67, + "def": 60, + "spa": 35, + "spd": 50, + "spe": 63 + }, + "abilities": { + "0": "Swift Swim", + "1": "Water Veil", + "H": "Lightning Rod" + }, + "heightm": 0.6, + "weightkg": 15, + "color": "Red", + "evos": [ + "seaking" + ], + "eggGroups": [ + "Water 2" + ] + }, + "seaking": { + "num": 119, + "species": "Seaking", + "types": [ + "Water" + ], + "baseStats": { + "hp": 80, + "atk": 92, + "def": 65, + "spa": 65, + "spd": 80, + "spe": 68 + }, + "abilities": { + "0": "Swift Swim", + "1": "Water Veil", + "H": "Lightning Rod" + }, + "heightm": 1.3, + "weightkg": 39, + "color": "Red", + "prevo": "goldeen", + "evoLevel": 33, + "eggGroups": [ + "Water 2" + ] + }, + "staryu": { + "num": 120, + "species": "Staryu", + "types": [ + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 30, + "atk": 45, + "def": 55, + "spa": 70, + "spd": 55, + "spe": 85 + }, + "abilities": { + "0": "Illuminate", + "1": "Natural Cure", + "H": "Analytic" + }, + "heightm": 0.8, + "weightkg": 34.5, + "color": "Brown", + "evos": [ + "starmie" + ], + "eggGroups": [ + "Water 3" + ] + }, + "starmie": { + "num": 121, + "species": "Starmie", + "types": [ + "Water", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 60, + "atk": 75, + "def": 85, + "spa": 100, + "spd": 85, + "spe": 115 + }, + "abilities": { + "0": "Illuminate", + "1": "Natural Cure", + "H": "Analytic" + }, + "heightm": 1.1, + "weightkg": 80, + "color": "Purple", + "prevo": "staryu", + "evoLevel": 1, + "eggGroups": [ + "Water 3" + ] + }, + "mrmime": { + "num": 122, + "species": "Mr. Mime", + "types": [ + "Psychic", + "Fairy" + ], + "baseStats": { + "hp": 40, + "atk": 45, + "def": 65, + "spa": 100, + "spd": 120, + "spe": 90 + }, + "abilities": { + "0": "Soundproof", + "1": "Filter", + "H": "Technician" + }, + "heightm": 1.3, + "weightkg": 54.5, + "color": "Pink", + "prevo": "mimejr", + "evoLevel": 1, + "evoMove": "Mimic", + "eggGroups": [ + "Human-Like" + ] + }, + "scyther": { + "num": 123, + "species": "Scyther", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 70, + "atk": 110, + "def": 80, + "spa": 55, + "spd": 80, + "spe": 105 + }, + "abilities": { + "0": "Swarm", + "1": "Technician", + "H": "Steadfast" + }, + "heightm": 1.5, + "weightkg": 56, + "color": "Green", + "evos": [ + "scizor" + ], + "eggGroups": [ + "Bug" + ] + }, + "jynx": { + "num": 124, + "species": "Jynx", + "types": [ + "Ice", + "Psychic" + ], + "gender": "F", + "baseStats": { + "hp": 65, + "atk": 50, + "def": 35, + "spa": 115, + "spd": 95, + "spe": 95 + }, + "abilities": { + "0": "Oblivious", + "1": "Forewarn", + "H": "Dry Skin" + }, + "heightm": 1.4, + "weightkg": 40.6, + "color": "Red", + "prevo": "smoochum", + "evoLevel": 30, + "eggGroups": [ + "Human-Like" + ] + }, + "electabuzz": { + "num": 125, + "species": "Electabuzz", + "types": [ + "Electric" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 65, + "atk": 83, + "def": 57, + "spa": 95, + "spd": 85, + "spe": 105 + }, + "abilities": { + "0": "Static", + "H": "Vital Spirit" + }, + "heightm": 1.1, + "weightkg": 30, + "color": "Yellow", + "prevo": "elekid", + "evos": [ + "electivire" + ], + "evoLevel": 30, + "eggGroups": [ + "Human-Like" + ] + }, + "magmar": { + "num": 126, + "species": "Magmar", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 65, + "atk": 95, + "def": 57, + "spa": 100, + "spd": 85, + "spe": 93 + }, + "abilities": { + "0": "Flame Body", + "H": "Vital Spirit" + }, + "heightm": 1.3, + "weightkg": 44.5, + "color": "Red", + "prevo": "magby", + "evos": [ + "magmortar" + ], + "evoLevel": 30, + "eggGroups": [ + "Human-Like" + ] + }, + "pinsir": { + "num": 127, + "species": "Pinsir", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 65, + "atk": 125, + "def": 100, + "spa": 55, + "spd": 70, + "spe": 85 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Mold Breaker", + "H": "Moxie" + }, + "heightm": 1.5, + "weightkg": 55, + "color": "Brown", + "eggGroups": [ + "Bug" + ], + "otherFormes": [ + "pinsirmega" + ] + }, + "pinsirmega": { + "num": 127, + "species": "Pinsir-Mega", + "baseSpecies": "Pinsir", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 65, + "atk": 155, + "def": 120, + "spa": 65, + "spd": 90, + "spe": 105 + }, + "abilities": { + "0": "Aerilate" + }, + "heightm": 1.7, + "weightkg": 59, + "color": "Brown", + "eggGroups": [ + "Bug" + ] + }, + "tauros": { + "num": 128, + "species": "Tauros", + "types": [ + "Normal" + ], + "gender": "M", + "baseStats": { + "hp": 75, + "atk": 100, + "def": 95, + "spa": 40, + "spd": 70, + "spe": 110 + }, + "abilities": { + "0": "Intimidate", + "1": "Anger Point", + "H": "Sheer Force" + }, + "heightm": 1.4, + "weightkg": 88.4, + "color": "Brown", + "eggGroups": [ + "Field" + ] + }, + "magikarp": { + "num": 129, + "species": "Magikarp", + "types": [ + "Water" + ], + "baseStats": { + "hp": 20, + "atk": 10, + "def": 55, + "spa": 15, + "spd": 20, + "spe": 80 + }, + "abilities": { + "0": "Swift Swim", + "H": "Rattled" + }, + "heightm": 0.9, + "weightkg": 10, + "color": "Red", + "evos": [ + "gyarados" + ], + "eggGroups": [ + "Water 2", + "Dragon" + ] + }, + "gyarados": { + "num": 130, + "species": "Gyarados", + "types": [ + "Water", + "Flying" + ], + "baseStats": { + "hp": 95, + "atk": 125, + "def": 79, + "spa": 60, + "spd": 100, + "spe": 81 + }, + "abilities": { + "0": "Intimidate", + "H": "Moxie" + }, + "heightm": 6.5, + "weightkg": 235, + "color": "Blue", + "prevo": "magikarp", + "evoLevel": 20, + "eggGroups": [ + "Water 2", + "Dragon" + ], + "otherFormes": [ + "gyaradosmega" + ] + }, + "gyaradosmega": { + "num": 130, + "species": "Gyarados-Mega", + "baseSpecies": "Gyarados", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Water", + "Dark" + ], + "baseStats": { + "hp": 95, + "atk": 155, + "def": 109, + "spa": 70, + "spd": 130, + "spe": 81 + }, + "abilities": { + "0": "Mold Breaker" + }, + "heightm": 6.5, + "weightkg": 305, + "color": "Blue", + "eggGroups": [ + "Water 2", + "Dragon" + ] + }, + "lapras": { + "num": 131, + "species": "Lapras", + "types": [ + "Water", + "Ice" + ], + "baseStats": { + "hp": 130, + "atk": 85, + "def": 80, + "spa": 85, + "spd": 95, + "spe": 60 + }, + "abilities": { + "0": "Water Absorb", + "1": "Shell Armor", + "H": "Hydration" + }, + "heightm": 2.5, + "weightkg": 220, + "color": "Blue", + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "ditto": { + "num": 132, + "species": "Ditto", + "types": [ + "Normal" + ], + "gender": "N", + "baseStats": { + "hp": 48, + "atk": 48, + "def": 48, + "spa": 48, + "spd": 48, + "spe": 48 + }, + "abilities": { + "0": "Limber", + "H": "Imposter" + }, + "heightm": 0.3, + "weightkg": 4, + "color": "Purple", + "eggGroups": [ + "Ditto" + ] + }, + "eevee": { + "num": 133, + "species": "Eevee", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 55, + "atk": 55, + "def": 50, + "spa": 45, + "spd": 65, + "spe": 55 + }, + "abilities": { + "0": "Run Away", + "1": "Adaptability", + "H": "Anticipation" + }, + "heightm": 0.3, + "weightkg": 6.5, + "color": "Brown", + "evos": [ + "vaporeon", + "jolteon", + "flareon", + "espeon", + "umbreon", + "leafeon", + "glaceon", + "sylveon" + ], + "eggGroups": [ + "Field" + ] + }, + "vaporeon": { + "num": 134, + "species": "Vaporeon", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 130, + "atk": 65, + "def": 60, + "spa": 110, + "spd": 95, + "spe": 65 + }, + "abilities": { + "0": "Water Absorb", + "H": "Hydration" + }, + "heightm": 1, + "weightkg": 29, + "color": "Blue", + "prevo": "eevee", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "jolteon": { + "num": 135, + "species": "Jolteon", + "types": [ + "Electric" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 65, + "atk": 65, + "def": 60, + "spa": 110, + "spd": 95, + "spe": 130 + }, + "abilities": { + "0": "Volt Absorb", + "H": "Quick Feet" + }, + "heightm": 0.8, + "weightkg": 24.5, + "color": "Yellow", + "prevo": "eevee", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "flareon": { + "num": 136, + "species": "Flareon", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 65, + "atk": 130, + "def": 60, + "spa": 95, + "spd": 110, + "spe": 65 + }, + "abilities": { + "0": "Flash Fire", + "H": "Guts" + }, + "heightm": 0.9, + "weightkg": 25, + "color": "Red", + "prevo": "eevee", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "porygon": { + "num": 137, + "species": "Porygon", + "types": [ + "Normal" + ], + "gender": "N", + "baseStats": { + "hp": 65, + "atk": 60, + "def": 70, + "spa": 85, + "spd": 75, + "spe": 40 + }, + "abilities": { + "0": "Trace", + "1": "Download", + "H": "Analytic" + }, + "heightm": 0.8, + "weightkg": 36.5, + "color": "Pink", + "evos": [ + "porygon2" + ], + "eggGroups": [ + "Mineral" + ] + }, + "omanyte": { + "num": 138, + "species": "Omanyte", + "types": [ + "Rock", + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 35, + "atk": 40, + "def": 100, + "spa": 90, + "spd": 55, + "spe": 35 + }, + "abilities": { + "0": "Swift Swim", + "1": "Shell Armor", + "H": "Weak Armor" + }, + "heightm": 0.4, + "weightkg": 7.5, + "color": "Blue", + "evos": [ + "omastar" + ], + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "omastar": { + "num": 139, + "species": "Omastar", + "types": [ + "Rock", + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 70, + "atk": 60, + "def": 125, + "spa": 115, + "spd": 70, + "spe": 55 + }, + "abilities": { + "0": "Swift Swim", + "1": "Shell Armor", + "H": "Weak Armor" + }, + "heightm": 1, + "weightkg": 35, + "color": "Blue", + "prevo": "omanyte", + "evoLevel": 40, + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "kabuto": { + "num": 140, + "species": "Kabuto", + "types": [ + "Rock", + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 30, + "atk": 80, + "def": 90, + "spa": 55, + "spd": 45, + "spe": 55 + }, + "abilities": { + "0": "Swift Swim", + "1": "Battle Armor", + "H": "Weak Armor" + }, + "heightm": 0.5, + "weightkg": 11.5, + "color": "Brown", + "evos": [ + "kabutops" + ], + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "kabutops": { + "num": 141, + "species": "Kabutops", + "types": [ + "Rock", + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 60, + "atk": 115, + "def": 105, + "spa": 65, + "spd": 70, + "spe": 80 + }, + "abilities": { + "0": "Swift Swim", + "1": "Battle Armor", + "H": "Weak Armor" + }, + "heightm": 1.3, + "weightkg": 40.5, + "color": "Brown", + "prevo": "kabuto", + "evoLevel": 40, + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "aerodactyl": { + "num": 142, + "species": "Aerodactyl", + "types": [ + "Rock", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 80, + "atk": 105, + "def": 65, + "spa": 60, + "spd": 75, + "spe": 130 + }, + "abilities": { + "0": "Rock Head", + "1": "Pressure", + "H": "Unnerve" + }, + "heightm": 1.8, + "weightkg": 59, + "color": "Purple", + "eggGroups": [ + "Flying" + ], + "otherFormes": [ + "aerodactylmega" + ] + }, + "aerodactylmega": { + "num": 142, + "species": "Aerodactyl-Mega", + "baseSpecies": "Aerodactyl", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Rock", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 80, + "atk": 135, + "def": 85, + "spa": 70, + "spd": 95, + "spe": 150 + }, + "abilities": { + "0": "Tough Claws" + }, + "heightm": 2.1, + "weightkg": 79, + "color": "Purple", + "eggGroups": [ + "Flying" + ] + }, + "snorlax": { + "num": 143, + "species": "Snorlax", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 160, + "atk": 110, + "def": 65, + "spa": 65, + "spd": 110, + "spe": 30 + }, + "abilities": { + "0": "Immunity", + "1": "Thick Fat", + "H": "Gluttony" + }, + "heightm": 2.1, + "weightkg": 460, + "color": "Black", + "prevo": "munchlax", + "evoLevel": 1, + "eggGroups": [ + "Monster" + ] + }, + "articuno": { + "num": 144, + "species": "Articuno", + "types": [ + "Ice", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 90, + "atk": 85, + "def": 100, + "spa": 95, + "spd": 125, + "spe": 85 + }, + "abilities": { + "0": "Pressure", + "H": "Snow Cloak" + }, + "heightm": 1.7, + "weightkg": 55.4, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "zapdos": { + "num": 145, + "species": "Zapdos", + "types": [ + "Electric", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 90, + "atk": 90, + "def": 85, + "spa": 125, + "spd": 90, + "spe": 100 + }, + "abilities": { + "0": "Pressure", + "H": "Static" + }, + "heightm": 1.6, + "weightkg": 52.6, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "moltres": { + "num": 146, + "species": "Moltres", + "types": [ + "Fire", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 90, + "atk": 100, + "def": 90, + "spa": 125, + "spd": 85, + "spe": 90 + }, + "abilities": { + "0": "Pressure", + "H": "Flame Body" + }, + "heightm": 2, + "weightkg": 60, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "dratini": { + "num": 147, + "species": "Dratini", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 41, + "atk": 64, + "def": 45, + "spa": 50, + "spd": 50, + "spe": 50 + }, + "abilities": { + "0": "Shed Skin", + "H": "Marvel Scale" + }, + "heightm": 1.8, + "weightkg": 3.3, + "color": "Blue", + "evos": [ + "dragonair" + ], + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "dragonair": { + "num": 148, + "species": "Dragonair", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 61, + "atk": 84, + "def": 65, + "spa": 70, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Shed Skin", + "H": "Marvel Scale" + }, + "heightm": 4, + "weightkg": 16.5, + "color": "Blue", + "prevo": "dratini", + "evos": [ + "dragonite" + ], + "evoLevel": 30, + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "dragonite": { + "num": 149, + "species": "Dragonite", + "types": [ + "Dragon", + "Flying" + ], + "baseStats": { + "hp": 91, + "atk": 134, + "def": 95, + "spa": 100, + "spd": 100, + "spe": 80 + }, + "abilities": { + "0": "Inner Focus", + "H": "Multiscale" + }, + "heightm": 2.2, + "weightkg": 210, + "color": "Brown", + "prevo": "dragonair", + "evoLevel": 55, + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "mewtwo": { + "num": 150, + "species": "Mewtwo", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 106, + "atk": 110, + "def": 90, + "spa": 154, + "spd": 90, + "spe": 130 + }, + "abilities": { + "0": "Pressure", + "H": "Unnerve" + }, + "heightm": 2, + "weightkg": 122, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "mewtwomegax", + "mewtwomegay" + ] + }, + "mewtwomegax": { + "num": 150, + "species": "Mewtwo-Mega-X", + "baseSpecies": "Mewtwo", + "forme": "Mega-X", + "formeLetter": "M", + "types": [ + "Psychic", + "Fighting" + ], + "gender": "N", + "baseStats": { + "hp": 106, + "atk": 190, + "def": 100, + "spa": 154, + "spd": 100, + "spe": 130 + }, + "abilities": { + "0": "Steadfast" + }, + "heightm": 2.3, + "weightkg": 127, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ] + }, + "mewtwomegay": { + "num": 150, + "species": "Mewtwo-Mega-Y", + "baseSpecies": "Mewtwo", + "forme": "Mega-Y", + "formeLetter": "M", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 106, + "atk": 150, + "def": 70, + "spa": 194, + "spd": 120, + "spe": 140 + }, + "abilities": { + "0": "Insomnia" + }, + "heightm": 1.5, + "weightkg": 33, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ] + }, + "mew": { + "num": 151, + "species": "Mew", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 100, + "def": 100, + "spa": 100, + "spd": 100, + "spe": 100 + }, + "abilities": { + "0": "Synchronize" + }, + "heightm": 0.4, + "weightkg": 4, + "color": "Pink", + "eggGroups": [ + "Undiscovered" + ] + }, + "chikorita": { + "num": 152, + "species": "Chikorita", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 45, + "atk": 49, + "def": 65, + "spa": 49, + "spd": 65, + "spe": 45 + }, + "abilities": { + "0": "Overgrow", + "H": "Leaf Guard" + }, + "heightm": 0.9, + "weightkg": 6.4, + "color": "Green", + "evos": [ + "bayleef" + ], + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "bayleef": { + "num": 153, + "species": "Bayleef", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 60, + "atk": 62, + "def": 80, + "spa": 63, + "spd": 80, + "spe": 60 + }, + "abilities": { + "0": "Overgrow", + "H": "Leaf Guard" + }, + "heightm": 1.2, + "weightkg": 15.8, + "color": "Green", + "prevo": "chikorita", + "evos": [ + "meganium" + ], + "evoLevel": 16, + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "meganium": { + "num": 154, + "species": "Meganium", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 80, + "atk": 82, + "def": 100, + "spa": 83, + "spd": 100, + "spe": 80 + }, + "abilities": { + "0": "Overgrow", + "H": "Leaf Guard" + }, + "heightm": 1.8, + "weightkg": 100.5, + "color": "Green", + "prevo": "bayleef", + "evoLevel": 32, + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "cyndaquil": { + "num": 155, + "species": "Cyndaquil", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 39, + "atk": 52, + "def": 43, + "spa": 60, + "spd": 50, + "spe": 65 + }, + "abilities": { + "0": "Blaze", + "H": "Flash Fire" + }, + "heightm": 0.5, + "weightkg": 7.9, + "color": "Yellow", + "evos": [ + "quilava" + ], + "eggGroups": [ + "Field" + ] + }, + "quilava": { + "num": 156, + "species": "Quilava", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 58, + "atk": 64, + "def": 58, + "spa": 80, + "spd": 65, + "spe": 80 + }, + "abilities": { + "0": "Blaze", + "H": "Flash Fire" + }, + "heightm": 0.9, + "weightkg": 19, + "color": "Yellow", + "prevo": "cyndaquil", + "evos": [ + "typhlosion" + ], + "evoLevel": 14, + "eggGroups": [ + "Field" + ] + }, + "typhlosion": { + "num": 157, + "species": "Typhlosion", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 78, + "atk": 84, + "def": 78, + "spa": 109, + "spd": 85, + "spe": 100 + }, + "abilities": { + "0": "Blaze", + "H": "Flash Fire" + }, + "heightm": 1.7, + "weightkg": 79.5, + "color": "Yellow", + "prevo": "quilava", + "evoLevel": 36, + "eggGroups": [ + "Field" + ] + }, + "totodile": { + "num": 158, + "species": "Totodile", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 50, + "atk": 65, + "def": 64, + "spa": 44, + "spd": 48, + "spe": 43 + }, + "abilities": { + "0": "Torrent", + "H": "Sheer Force" + }, + "heightm": 0.6, + "weightkg": 9.5, + "color": "Blue", + "evos": [ + "croconaw" + ], + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "croconaw": { + "num": 159, + "species": "Croconaw", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 65, + "atk": 80, + "def": 80, + "spa": 59, + "spd": 63, + "spe": 58 + }, + "abilities": { + "0": "Torrent", + "H": "Sheer Force" + }, + "heightm": 1.1, + "weightkg": 25, + "color": "Blue", + "prevo": "totodile", + "evos": [ + "feraligatr" + ], + "evoLevel": 18, + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "feraligatr": { + "num": 160, + "species": "Feraligatr", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 85, + "atk": 105, + "def": 100, + "spa": 79, + "spd": 83, + "spe": 78 + }, + "abilities": { + "0": "Torrent", + "H": "Sheer Force" + }, + "heightm": 2.3, + "weightkg": 88.8, + "color": "Blue", + "prevo": "croconaw", + "evoLevel": 30, + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "sentret": { + "num": 161, + "species": "Sentret", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 35, + "atk": 46, + "def": 34, + "spa": 35, + "spd": 45, + "spe": 20 + }, + "abilities": { + "0": "Run Away", + "1": "Keen Eye", + "H": "Frisk" + }, + "heightm": 0.8, + "weightkg": 6, + "color": "Brown", + "evos": [ + "furret" + ], + "eggGroups": [ + "Field" + ] + }, + "furret": { + "num": 162, + "species": "Furret", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 85, + "atk": 76, + "def": 64, + "spa": 45, + "spd": 55, + "spe": 90 + }, + "abilities": { + "0": "Run Away", + "1": "Keen Eye", + "H": "Frisk" + }, + "heightm": 1.8, + "weightkg": 32.5, + "color": "Brown", + "prevo": "sentret", + "evoLevel": 15, + "eggGroups": [ + "Field" + ] + }, + "hoothoot": { + "num": 163, + "species": "Hoothoot", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 60, + "atk": 30, + "def": 30, + "spa": 36, + "spd": 56, + "spe": 50 + }, + "abilities": { + "0": "Insomnia", + "1": "Keen Eye", + "H": "Tinted Lens" + }, + "heightm": 0.7, + "weightkg": 21.2, + "color": "Brown", + "evos": [ + "noctowl" + ], + "eggGroups": [ + "Flying" + ] + }, + "noctowl": { + "num": 164, + "species": "Noctowl", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 100, + "atk": 50, + "def": 50, + "spa": 76, + "spd": 96, + "spe": 70 + }, + "abilities": { + "0": "Insomnia", + "1": "Keen Eye", + "H": "Tinted Lens" + }, + "heightm": 1.6, + "weightkg": 40.8, + "color": "Brown", + "prevo": "hoothoot", + "evoLevel": 20, + "eggGroups": [ + "Flying" + ] + }, + "ledyba": { + "num": 165, + "species": "Ledyba", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 40, + "atk": 20, + "def": 30, + "spa": 40, + "spd": 80, + "spe": 55 + }, + "abilities": { + "0": "Swarm", + "1": "Early Bird", + "H": "Rattled" + }, + "heightm": 1, + "weightkg": 10.8, + "color": "Red", + "evos": [ + "ledian" + ], + "eggGroups": [ + "Bug" + ] + }, + "ledian": { + "num": 166, + "species": "Ledian", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 55, + "atk": 35, + "def": 50, + "spa": 55, + "spd": 110, + "spe": 85 + }, + "abilities": { + "0": "Swarm", + "1": "Early Bird", + "H": "Iron Fist" + }, + "heightm": 1.4, + "weightkg": 35.6, + "color": "Red", + "prevo": "ledyba", + "evoLevel": 18, + "eggGroups": [ + "Bug" + ] + }, + "spinarak": { + "num": 167, + "species": "Spinarak", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 40, + "atk": 60, + "def": 40, + "spa": 40, + "spd": 40, + "spe": 30 + }, + "abilities": { + "0": "Swarm", + "1": "Insomnia", + "H": "Sniper" + }, + "heightm": 0.5, + "weightkg": 8.5, + "color": "Green", + "evos": [ + "ariados" + ], + "eggGroups": [ + "Bug" + ] + }, + "ariados": { + "num": 168, + "species": "Ariados", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 70, + "atk": 90, + "def": 70, + "spa": 60, + "spd": 60, + "spe": 40 + }, + "abilities": { + "0": "Swarm", + "1": "Insomnia", + "H": "Sniper" + }, + "heightm": 1.1, + "weightkg": 33.5, + "color": "Red", + "prevo": "spinarak", + "evoLevel": 22, + "eggGroups": [ + "Bug" + ] + }, + "crobat": { + "num": 169, + "species": "Crobat", + "types": [ + "Poison", + "Flying" + ], + "baseStats": { + "hp": 85, + "atk": 90, + "def": 80, + "spa": 70, + "spd": 80, + "spe": 130 + }, + "abilities": { + "0": "Inner Focus", + "H": "Infiltrator" + }, + "heightm": 1.8, + "weightkg": 75, + "color": "Purple", + "prevo": "golbat", + "evoLevel": 23, + "eggGroups": [ + "Flying" + ] + }, + "chinchou": { + "num": 170, + "species": "Chinchou", + "types": [ + "Water", + "Electric" + ], + "baseStats": { + "hp": 75, + "atk": 38, + "def": 38, + "spa": 56, + "spd": 56, + "spe": 67 + }, + "abilities": { + "0": "Volt Absorb", + "1": "Illuminate", + "H": "Water Absorb" + }, + "heightm": 0.5, + "weightkg": 12, + "color": "Blue", + "evos": [ + "lanturn" + ], + "eggGroups": [ + "Water 2" + ] + }, + "lanturn": { + "num": 171, + "species": "Lanturn", + "types": [ + "Water", + "Electric" + ], + "baseStats": { + "hp": 125, + "atk": 58, + "def": 58, + "spa": 76, + "spd": 76, + "spe": 67 + }, + "abilities": { + "0": "Volt Absorb", + "1": "Illuminate", + "H": "Water Absorb" + }, + "heightm": 1.2, + "weightkg": 22.5, + "color": "Blue", + "prevo": "chinchou", + "evoLevel": 27, + "eggGroups": [ + "Water 2" + ] + }, + "pichu": { + "num": 172, + "species": "Pichu", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 20, + "atk": 40, + "def": 15, + "spa": 35, + "spd": 35, + "spe": 60 + }, + "abilities": { + "0": "Static", + "H": "Lightning Rod" + }, + "heightm": 0.3, + "weightkg": 2, + "color": "Yellow", + "evos": [ + "pikachu" + ], + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "pichuspikyeared" + ] + }, + "pichuspikyeared": { + "num": 172, + "species": "Pichu-Spiky-eared", + "baseSpecies": "Pichu", + "forme": "Spiky-eared", + "formeLetter": "S", + "types": [ + "Electric" + ], + "gender": "F", + "baseStats": { + "hp": 20, + "atk": 40, + "def": 15, + "spa": 35, + "spd": 35, + "spe": 60 + }, + "abilities": { + "0": "Static" + }, + "heightm": 0.3, + "weightkg": 2, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "cleffa": { + "num": 173, + "species": "Cleffa", + "types": [ + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 50, + "atk": 25, + "def": 28, + "spa": 45, + "spd": 55, + "spe": 15 + }, + "abilities": { + "0": "Cute Charm", + "1": "Magic Guard", + "H": "Friend Guard" + }, + "heightm": 0.3, + "weightkg": 3, + "color": "Pink", + "evos": [ + "clefairy" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "igglybuff": { + "num": 174, + "species": "Igglybuff", + "types": [ + "Normal", + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 90, + "atk": 30, + "def": 15, + "spa": 40, + "spd": 20, + "spe": 15 + }, + "abilities": { + "0": "Cute Charm", + "1": "Competitive", + "H": "Friend Guard" + }, + "heightm": 0.3, + "weightkg": 1, + "color": "Pink", + "evos": [ + "jigglypuff" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "togepi": { + "num": 175, + "species": "Togepi", + "types": [ + "Fairy" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 35, + "atk": 20, + "def": 65, + "spa": 40, + "spd": 65, + "spe": 20 + }, + "abilities": { + "0": "Hustle", + "1": "Serene Grace", + "H": "Super Luck" + }, + "heightm": 0.3, + "weightkg": 1.5, + "color": "White", + "evos": [ + "togetic" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "togetic": { + "num": 176, + "species": "Togetic", + "types": [ + "Fairy", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 55, + "atk": 40, + "def": 85, + "spa": 80, + "spd": 105, + "spe": 40 + }, + "abilities": { + "0": "Hustle", + "1": "Serene Grace", + "H": "Super Luck" + }, + "heightm": 0.6, + "weightkg": 3.2, + "color": "White", + "prevo": "togepi", + "evos": [ + "togekiss" + ], + "evoLevel": 2, + "eggGroups": [ + "Flying", + "Fairy" + ] + }, + "natu": { + "num": 177, + "species": "Natu", + "types": [ + "Psychic", + "Flying" + ], + "baseStats": { + "hp": 40, + "atk": 50, + "def": 45, + "spa": 70, + "spd": 45, + "spe": 70 + }, + "abilities": { + "0": "Synchronize", + "1": "Early Bird", + "H": "Magic Bounce" + }, + "heightm": 0.2, + "weightkg": 2, + "color": "Green", + "evos": [ + "xatu" + ], + "eggGroups": [ + "Flying" + ] + }, + "xatu": { + "num": 178, + "species": "Xatu", + "types": [ + "Psychic", + "Flying" + ], + "baseStats": { + "hp": 65, + "atk": 75, + "def": 70, + "spa": 95, + "spd": 70, + "spe": 95 + }, + "abilities": { + "0": "Synchronize", + "1": "Early Bird", + "H": "Magic Bounce" + }, + "heightm": 1.5, + "weightkg": 15, + "color": "Green", + "prevo": "natu", + "evoLevel": 25, + "eggGroups": [ + "Flying" + ] + }, + "mareep": { + "num": 179, + "species": "Mareep", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 55, + "atk": 40, + "def": 40, + "spa": 65, + "spd": 45, + "spe": 35 + }, + "abilities": { + "0": "Static", + "H": "Plus" + }, + "heightm": 0.6, + "weightkg": 7.8, + "color": "White", + "evos": [ + "flaaffy" + ], + "eggGroups": [ + "Monster", + "Field" + ] + }, + "flaaffy": { + "num": 180, + "species": "Flaaffy", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 70, + "atk": 55, + "def": 55, + "spa": 80, + "spd": 60, + "spe": 45 + }, + "abilities": { + "0": "Static", + "H": "Plus" + }, + "heightm": 0.8, + "weightkg": 13.3, + "color": "Pink", + "prevo": "mareep", + "evos": [ + "ampharos" + ], + "evoLevel": 15, + "eggGroups": [ + "Monster", + "Field" + ] + }, + "ampharos": { + "num": 181, + "species": "Ampharos", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 90, + "atk": 75, + "def": 85, + "spa": 115, + "spd": 90, + "spe": 55 + }, + "abilities": { + "0": "Static", + "H": "Plus" + }, + "heightm": 1.4, + "weightkg": 61.5, + "color": "Yellow", + "prevo": "flaaffy", + "evoLevel": 30, + "eggGroups": [ + "Monster", + "Field" + ], + "otherFormes": [ + "ampharosmega" + ] + }, + "ampharosmega": { + "num": 181, + "species": "Ampharos-Mega", + "baseSpecies": "Ampharos", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Electric", + "Dragon" + ], + "baseStats": { + "hp": 90, + "atk": 95, + "def": 105, + "spa": 165, + "spd": 110, + "spe": 45 + }, + "abilities": { + "0": "Mold Breaker" + }, + "heightm": 1.4, + "weightkg": 61.5, + "color": "Yellow", + "eggGroups": [ + "Monster", + "Field" + ] + }, + "bellossom": { + "num": 182, + "species": "Bellossom", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 75, + "atk": 80, + "def": 95, + "spa": 90, + "spd": 100, + "spe": 50 + }, + "abilities": { + "0": "Chlorophyll", + "H": "Healer" + }, + "heightm": 0.4, + "weightkg": 5.8, + "color": "Green", + "prevo": "gloom", + "evoLevel": 21, + "eggGroups": [ + "Grass" + ] + }, + "marill": { + "num": 183, + "species": "Marill", + "types": [ + "Water", + "Fairy" + ], + "baseStats": { + "hp": 70, + "atk": 20, + "def": 50, + "spa": 20, + "spd": 50, + "spe": 40 + }, + "abilities": { + "0": "Thick Fat", + "1": "Huge Power", + "H": "Sap Sipper" + }, + "heightm": 0.4, + "weightkg": 8.5, + "color": "Blue", + "prevo": "azurill", + "evos": [ + "azumarill" + ], + "evoLevel": 1, + "eggGroups": [ + "Water 1", + "Fairy" + ] + }, + "azumarill": { + "num": 184, + "species": "Azumarill", + "types": [ + "Water", + "Fairy" + ], + "baseStats": { + "hp": 100, + "atk": 50, + "def": 80, + "spa": 60, + "spd": 80, + "spe": 50 + }, + "abilities": { + "0": "Thick Fat", + "1": "Huge Power", + "H": "Sap Sipper" + }, + "heightm": 0.8, + "weightkg": 28.5, + "color": "Blue", + "prevo": "marill", + "evoLevel": 18, + "eggGroups": [ + "Water 1", + "Fairy" + ] + }, + "sudowoodo": { + "num": 185, + "species": "Sudowoodo", + "types": [ + "Rock" + ], + "baseStats": { + "hp": 70, + "atk": 100, + "def": 115, + "spa": 30, + "spd": 65, + "spe": 30 + }, + "abilities": { + "0": "Sturdy", + "1": "Rock Head", + "H": "Rattled" + }, + "heightm": 1.2, + "weightkg": 38, + "color": "Brown", + "prevo": "bonsly", + "evoLevel": 1, + "evoMove": "Mimic", + "eggGroups": [ + "Mineral" + ] + }, + "politoed": { + "num": 186, + "species": "Politoed", + "types": [ + "Water" + ], + "baseStats": { + "hp": 90, + "atk": 75, + "def": 75, + "spa": 90, + "spd": 100, + "spe": 70 + }, + "abilities": { + "0": "Water Absorb", + "1": "Damp", + "H": "Drizzle" + }, + "heightm": 1.1, + "weightkg": 33.9, + "color": "Green", + "prevo": "poliwhirl", + "evoLevel": 25, + "eggGroups": [ + "Water 1" + ] + }, + "hoppip": { + "num": 187, + "species": "Hoppip", + "types": [ + "Grass", + "Flying" + ], + "baseStats": { + "hp": 35, + "atk": 35, + "def": 40, + "spa": 35, + "spd": 55, + "spe": 50 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Leaf Guard", + "H": "Infiltrator" + }, + "heightm": 0.4, + "weightkg": 0.5, + "color": "Pink", + "evos": [ + "skiploom" + ], + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "skiploom": { + "num": 188, + "species": "Skiploom", + "types": [ + "Grass", + "Flying" + ], + "baseStats": { + "hp": 55, + "atk": 45, + "def": 50, + "spa": 45, + "spd": 65, + "spe": 80 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Leaf Guard", + "H": "Infiltrator" + }, + "heightm": 0.6, + "weightkg": 1, + "color": "Green", + "prevo": "hoppip", + "evos": [ + "jumpluff" + ], + "evoLevel": 18, + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "jumpluff": { + "num": 189, + "species": "Jumpluff", + "types": [ + "Grass", + "Flying" + ], + "baseStats": { + "hp": 75, + "atk": 55, + "def": 70, + "spa": 55, + "spd": 95, + "spe": 110 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Leaf Guard", + "H": "Infiltrator" + }, + "heightm": 0.8, + "weightkg": 3, + "color": "Blue", + "prevo": "skiploom", + "evoLevel": 27, + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "aipom": { + "num": 190, + "species": "Aipom", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 55, + "atk": 70, + "def": 55, + "spa": 40, + "spd": 55, + "spe": 85 + }, + "abilities": { + "0": "Run Away", + "1": "Pickup", + "H": "Skill Link" + }, + "heightm": 0.8, + "weightkg": 11.5, + "color": "Purple", + "evos": [ + "ambipom" + ], + "eggGroups": [ + "Field" + ] + }, + "sunkern": { + "num": 191, + "species": "Sunkern", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 30, + "atk": 30, + "def": 30, + "spa": 30, + "spd": 30, + "spe": 30 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Solar Power", + "H": "Early Bird" + }, + "heightm": 0.3, + "weightkg": 1.8, + "color": "Yellow", + "evos": [ + "sunflora" + ], + "eggGroups": [ + "Grass" + ] + }, + "sunflora": { + "num": 192, + "species": "Sunflora", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 75, + "atk": 75, + "def": 55, + "spa": 105, + "spd": 85, + "spe": 30 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Solar Power", + "H": "Early Bird" + }, + "heightm": 0.8, + "weightkg": 8.5, + "color": "Yellow", + "prevo": "sunkern", + "evoLevel": 1, + "eggGroups": [ + "Grass" + ] + }, + "yanma": { + "num": 193, + "species": "Yanma", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 65, + "atk": 65, + "def": 45, + "spa": 75, + "spd": 45, + "spe": 95 + }, + "abilities": { + "0": "Speed Boost", + "1": "Compound Eyes", + "H": "Frisk" + }, + "heightm": 1.2, + "weightkg": 38, + "color": "Red", + "evos": [ + "yanmega" + ], + "eggGroups": [ + "Bug" + ] + }, + "wooper": { + "num": 194, + "species": "Wooper", + "types": [ + "Water", + "Ground" + ], + "baseStats": { + "hp": 55, + "atk": 45, + "def": 45, + "spa": 25, + "spd": 25, + "spe": 15 + }, + "abilities": { + "0": "Damp", + "1": "Water Absorb", + "H": "Unaware" + }, + "heightm": 0.4, + "weightkg": 8.5, + "color": "Blue", + "evos": [ + "quagsire" + ], + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "quagsire": { + "num": 195, + "species": "Quagsire", + "types": [ + "Water", + "Ground" + ], + "baseStats": { + "hp": 95, + "atk": 85, + "def": 85, + "spa": 65, + "spd": 65, + "spe": 35 + }, + "abilities": { + "0": "Damp", + "1": "Water Absorb", + "H": "Unaware" + }, + "heightm": 1.4, + "weightkg": 75, + "color": "Blue", + "prevo": "wooper", + "evoLevel": 20, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "espeon": { + "num": 196, + "species": "Espeon", + "types": [ + "Psychic" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 65, + "atk": 65, + "def": 60, + "spa": 130, + "spd": 95, + "spe": 110 + }, + "abilities": { + "0": "Synchronize", + "H": "Magic Bounce" + }, + "heightm": 0.9, + "weightkg": 26.5, + "color": "Purple", + "prevo": "eevee", + "evoLevel": 2, + "eggGroups": [ + "Field" + ] + }, + "umbreon": { + "num": 197, + "species": "Umbreon", + "types": [ + "Dark" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 95, + "atk": 65, + "def": 110, + "spa": 60, + "spd": 130, + "spe": 65 + }, + "abilities": { + "0": "Synchronize", + "H": "Inner Focus" + }, + "heightm": 1, + "weightkg": 27, + "color": "Black", + "prevo": "eevee", + "evoLevel": 2, + "eggGroups": [ + "Field" + ] + }, + "murkrow": { + "num": 198, + "species": "Murkrow", + "types": [ + "Dark", + "Flying" + ], + "baseStats": { + "hp": 60, + "atk": 85, + "def": 42, + "spa": 85, + "spd": 42, + "spe": 91 + }, + "abilities": { + "0": "Insomnia", + "1": "Super Luck", + "H": "Prankster" + }, + "heightm": 0.5, + "weightkg": 2.1, + "color": "Black", + "evos": [ + "honchkrow" + ], + "eggGroups": [ + "Flying" + ] + }, + "slowking": { + "num": 199, + "species": "Slowking", + "types": [ + "Water", + "Psychic" + ], + "baseStats": { + "hp": 95, + "atk": 75, + "def": 80, + "spa": 100, + "spd": 110, + "spe": 30 + }, + "abilities": { + "0": "Oblivious", + "1": "Own Tempo", + "H": "Regenerator" + }, + "heightm": 2, + "weightkg": 79.5, + "color": "Pink", + "prevo": "slowpoke", + "evoLevel": 1, + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "misdreavus": { + "num": 200, + "species": "Misdreavus", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 60, + "atk": 60, + "def": 60, + "spa": 85, + "spd": 85, + "spe": 85 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.7, + "weightkg": 1, + "color": "Gray", + "evos": [ + "mismagius" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "unown": { + "num": 201, + "species": "Unown", + "baseForme": "A", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 48, + "atk": 72, + "def": 48, + "spa": 72, + "spd": 48, + "spe": 48 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.5, + "weightkg": 5, + "color": "Black", + "eggGroups": [ + "Undiscovered" + ], + "otherForms": [ + "unownb", + "unownc", + "unownd", + "unowne", + "unownf", + "unowng", + "unownh", + "unowni", + "unownj", + "unownk", + "unownl", + "unownm", + "unownn", + "unowno", + "unownp", + "unownq", + "unownr", + "unowns", + "unownt", + "unownu", + "unownv", + "unownw", + "unownx", + "unowny", + "unownz", + "unownexclamation", + "unownquestion" + ] + }, + "wobbuffet": { + "num": 202, + "species": "Wobbuffet", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 190, + "atk": 33, + "def": 58, + "spa": 33, + "spd": 58, + "spe": 33 + }, + "abilities": { + "0": "Shadow Tag", + "H": "Telepathy" + }, + "heightm": 1.3, + "weightkg": 28.5, + "color": "Blue", + "prevo": "wynaut", + "evoLevel": 15, + "eggGroups": [ + "Amorphous" + ] + }, + "girafarig": { + "num": 203, + "species": "Girafarig", + "types": [ + "Normal", + "Psychic" + ], + "baseStats": { + "hp": 70, + "atk": 80, + "def": 65, + "spa": 90, + "spd": 65, + "spe": 85 + }, + "abilities": { + "0": "Inner Focus", + "1": "Early Bird", + "H": "Sap Sipper" + }, + "heightm": 1.5, + "weightkg": 41.5, + "color": "Yellow", + "eggGroups": [ + "Field" + ] + }, + "pineco": { + "num": 204, + "species": "Pineco", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 50, + "atk": 65, + "def": 90, + "spa": 35, + "spd": 35, + "spe": 15 + }, + "abilities": { + "0": "Sturdy", + "H": "Overcoat" + }, + "heightm": 0.6, + "weightkg": 7.2, + "color": "Gray", + "evos": [ + "forretress" + ], + "eggGroups": [ + "Bug" + ] + }, + "forretress": { + "num": 205, + "species": "Forretress", + "types": [ + "Bug", + "Steel" + ], + "baseStats": { + "hp": 75, + "atk": 90, + "def": 140, + "spa": 60, + "spd": 60, + "spe": 40 + }, + "abilities": { + "0": "Sturdy", + "H": "Overcoat" + }, + "heightm": 1.2, + "weightkg": 125.8, + "color": "Purple", + "prevo": "pineco", + "evoLevel": 31, + "eggGroups": [ + "Bug" + ] + }, + "dunsparce": { + "num": 206, + "species": "Dunsparce", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 100, + "atk": 70, + "def": 70, + "spa": 65, + "spd": 65, + "spe": 45 + }, + "abilities": { + "0": "Serene Grace", + "1": "Run Away", + "H": "Rattled" + }, + "heightm": 1.5, + "weightkg": 14, + "color": "Yellow", + "eggGroups": [ + "Field" + ] + }, + "gligar": { + "num": 207, + "species": "Gligar", + "types": [ + "Ground", + "Flying" + ], + "baseStats": { + "hp": 65, + "atk": 75, + "def": 105, + "spa": 35, + "spd": 65, + "spe": 85 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Sand Veil", + "H": "Immunity" + }, + "heightm": 1.1, + "weightkg": 64.8, + "color": "Purple", + "evos": [ + "gliscor" + ], + "eggGroups": [ + "Bug" + ] + }, + "steelix": { + "num": 208, + "species": "Steelix", + "types": [ + "Steel", + "Ground" + ], + "baseStats": { + "hp": 75, + "atk": 85, + "def": 200, + "spa": 55, + "spd": 65, + "spe": 30 + }, + "abilities": { + "0": "Rock Head", + "1": "Sturdy", + "H": "Sheer Force" + }, + "heightm": 9.2, + "weightkg": 400, + "color": "Gray", + "prevo": "onix", + "evoLevel": 1, + "eggGroups": [ + "Mineral" + ], + "otherFormes": [ + "steelixmega" + ] + }, + "steelixmega": { + "num": 208, + "species": "Steelix-Mega", + "baseSpecies": "Steelix", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Steel", + "Ground" + ], + "baseStats": { + "hp": 75, + "atk": 125, + "def": 230, + "spa": 55, + "spd": 95, + "spe": 30 + }, + "abilities": { + "0": "Sand Force" + }, + "heightm": 10.5, + "weightkg": 740, + "color": "Gray", + "eggGroups": [ + "Mineral" + ] + }, + "snubbull": { + "num": 209, + "species": "Snubbull", + "types": [ + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 60, + "atk": 80, + "def": 50, + "spa": 40, + "spd": 40, + "spe": 30 + }, + "abilities": { + "0": "Intimidate", + "1": "Run Away", + "H": "Rattled" + }, + "heightm": 0.6, + "weightkg": 7.8, + "color": "Pink", + "evos": [ + "granbull" + ], + "eggGroups": [ + "Field", + "Fairy" + ] + }, + "granbull": { + "num": 210, + "species": "Granbull", + "types": [ + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 90, + "atk": 120, + "def": 75, + "spa": 60, + "spd": 60, + "spe": 45 + }, + "abilities": { + "0": "Intimidate", + "1": "Quick Feet", + "H": "Rattled" + }, + "heightm": 1.4, + "weightkg": 48.7, + "color": "Purple", + "prevo": "snubbull", + "evoLevel": 23, + "eggGroups": [ + "Field", + "Fairy" + ] + }, + "qwilfish": { + "num": 211, + "species": "Qwilfish", + "types": [ + "Water", + "Poison" + ], + "baseStats": { + "hp": 65, + "atk": 95, + "def": 75, + "spa": 55, + "spd": 55, + "spe": 85 + }, + "abilities": { + "0": "Poison Point", + "1": "Swift Swim", + "H": "Intimidate" + }, + "heightm": 0.5, + "weightkg": 3.9, + "color": "Gray", + "eggGroups": [ + "Water 2" + ] + }, + "scizor": { + "num": 212, + "species": "Scizor", + "types": [ + "Bug", + "Steel" + ], + "baseStats": { + "hp": 70, + "atk": 130, + "def": 100, + "spa": 55, + "spd": 80, + "spe": 65 + }, + "abilities": { + "0": "Swarm", + "1": "Technician", + "H": "Light Metal" + }, + "heightm": 1.8, + "weightkg": 118, + "color": "Red", + "prevo": "scyther", + "evoLevel": 1, + "eggGroups": [ + "Bug" + ], + "otherFormes": [ + "scizormega" + ] + }, + "scizormega": { + "num": 212, + "species": "Scizor-Mega", + "baseSpecies": "Scizor", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Bug", + "Steel" + ], + "baseStats": { + "hp": 70, + "atk": 150, + "def": 140, + "spa": 65, + "spd": 100, + "spe": 75 + }, + "abilities": { + "0": "Technician" + }, + "heightm": 2, + "weightkg": 125, + "color": "Red", + "eggGroups": [ + "Bug" + ] + }, + "shuckle": { + "num": 213, + "species": "Shuckle", + "types": [ + "Bug", + "Rock" + ], + "baseStats": { + "hp": 20, + "atk": 10, + "def": 230, + "spa": 10, + "spd": 230, + "spe": 5 + }, + "abilities": { + "0": "Sturdy", + "1": "Gluttony", + "H": "Contrary" + }, + "heightm": 0.6, + "weightkg": 20.5, + "color": "Yellow", + "eggGroups": [ + "Bug" + ] + }, + "heracross": { + "num": 214, + "species": "Heracross", + "types": [ + "Bug", + "Fighting" + ], + "baseStats": { + "hp": 80, + "atk": 125, + "def": 75, + "spa": 40, + "spd": 95, + "spe": 85 + }, + "abilities": { + "0": "Swarm", + "1": "Guts", + "H": "Moxie" + }, + "heightm": 1.5, + "weightkg": 54, + "color": "Blue", + "eggGroups": [ + "Bug" + ], + "otherFormes": [ + "heracrossmega" + ] + }, + "heracrossmega": { + "num": 214, + "species": "Heracross-Mega", + "baseSpecies": "Heracross", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Bug", + "Fighting" + ], + "baseStats": { + "hp": 80, + "atk": 185, + "def": 115, + "spa": 40, + "spd": 105, + "spe": 75 + }, + "abilities": { + "0": "Skill Link" + }, + "heightm": 1.7, + "weightkg": 62.5, + "color": "Blue", + "eggGroups": [ + "Bug" + ] + }, + "sneasel": { + "num": 215, + "species": "Sneasel", + "types": [ + "Dark", + "Ice" + ], + "baseStats": { + "hp": 55, + "atk": 95, + "def": 55, + "spa": 35, + "spd": 75, + "spe": 115 + }, + "abilities": { + "0": "Inner Focus", + "1": "Keen Eye", + "H": "Pickpocket" + }, + "heightm": 0.9, + "weightkg": 28, + "color": "Black", + "evos": [ + "weavile" + ], + "eggGroups": [ + "Field" + ] + }, + "teddiursa": { + "num": 216, + "species": "Teddiursa", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 60, + "atk": 80, + "def": 50, + "spa": 50, + "spd": 50, + "spe": 40 + }, + "abilities": { + "0": "Pickup", + "1": "Quick Feet", + "H": "Honey Gather" + }, + "heightm": 0.6, + "weightkg": 8.8, + "color": "Brown", + "evos": [ + "ursaring" + ], + "eggGroups": [ + "Field" + ] + }, + "ursaring": { + "num": 217, + "species": "Ursaring", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 90, + "atk": 130, + "def": 75, + "spa": 75, + "spd": 75, + "spe": 55 + }, + "abilities": { + "0": "Guts", + "1": "Quick Feet", + "H": "Unnerve" + }, + "heightm": 1.8, + "weightkg": 125.8, + "color": "Brown", + "prevo": "teddiursa", + "evoLevel": 30, + "eggGroups": [ + "Field" + ] + }, + "slugma": { + "num": 218, + "species": "Slugma", + "types": [ + "Fire" + ], + "baseStats": { + "hp": 40, + "atk": 40, + "def": 40, + "spa": 70, + "spd": 40, + "spe": 20 + }, + "abilities": { + "0": "Magma Armor", + "1": "Flame Body", + "H": "Weak Armor" + }, + "heightm": 0.7, + "weightkg": 35, + "color": "Red", + "evos": [ + "magcargo" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "magcargo": { + "num": 219, + "species": "Magcargo", + "types": [ + "Fire", + "Rock" + ], + "baseStats": { + "hp": 50, + "atk": 50, + "def": 120, + "spa": 80, + "spd": 80, + "spe": 30 + }, + "abilities": { + "0": "Magma Armor", + "1": "Flame Body", + "H": "Weak Armor" + }, + "heightm": 0.8, + "weightkg": 55, + "color": "Red", + "prevo": "slugma", + "evoLevel": 38, + "eggGroups": [ + "Amorphous" + ] + }, + "swinub": { + "num": 220, + "species": "Swinub", + "types": [ + "Ice", + "Ground" + ], + "baseStats": { + "hp": 50, + "atk": 50, + "def": 40, + "spa": 30, + "spd": 30, + "spe": 50 + }, + "abilities": { + "0": "Oblivious", + "1": "Snow Cloak", + "H": "Thick Fat" + }, + "heightm": 0.4, + "weightkg": 6.5, + "color": "Brown", + "evos": [ + "piloswine" + ], + "eggGroups": [ + "Field" + ] + }, + "piloswine": { + "num": 221, + "species": "Piloswine", + "types": [ + "Ice", + "Ground" + ], + "baseStats": { + "hp": 100, + "atk": 100, + "def": 80, + "spa": 60, + "spd": 60, + "spe": 50 + }, + "abilities": { + "0": "Oblivious", + "1": "Snow Cloak", + "H": "Thick Fat" + }, + "heightm": 1.1, + "weightkg": 55.8, + "color": "Brown", + "prevo": "swinub", + "evos": [ + "mamoswine" + ], + "evoLevel": 33, + "eggGroups": [ + "Field" + ] + }, + "corsola": { + "num": 222, + "species": "Corsola", + "types": [ + "Water", + "Rock" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 55, + "atk": 55, + "def": 85, + "spa": 65, + "spd": 85, + "spe": 35 + }, + "abilities": { + "0": "Hustle", + "1": "Natural Cure", + "H": "Regenerator" + }, + "heightm": 0.6, + "weightkg": 5, + "color": "Pink", + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "remoraid": { + "num": 223, + "species": "Remoraid", + "types": [ + "Water" + ], + "baseStats": { + "hp": 35, + "atk": 65, + "def": 35, + "spa": 65, + "spd": 35, + "spe": 65 + }, + "abilities": { + "0": "Hustle", + "1": "Sniper", + "H": "Moody" + }, + "heightm": 0.6, + "weightkg": 12, + "color": "Gray", + "evos": [ + "octillery" + ], + "eggGroups": [ + "Water 1", + "Water 2" + ] + }, + "octillery": { + "num": 224, + "species": "Octillery", + "types": [ + "Water" + ], + "baseStats": { + "hp": 75, + "atk": 105, + "def": 75, + "spa": 105, + "spd": 75, + "spe": 45 + }, + "abilities": { + "0": "Suction Cups", + "1": "Sniper", + "H": "Moody" + }, + "heightm": 0.9, + "weightkg": 28.5, + "color": "Red", + "prevo": "remoraid", + "evoLevel": 25, + "eggGroups": [ + "Water 1", + "Water 2" + ] + }, + "delibird": { + "num": 225, + "species": "Delibird", + "types": [ + "Ice", + "Flying" + ], + "baseStats": { + "hp": 45, + "atk": 55, + "def": 45, + "spa": 65, + "spd": 45, + "spe": 75 + }, + "abilities": { + "0": "Vital Spirit", + "1": "Hustle", + "H": "Insomnia" + }, + "heightm": 0.9, + "weightkg": 16, + "color": "Red", + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "mantine": { + "num": 226, + "species": "Mantine", + "types": [ + "Water", + "Flying" + ], + "baseStats": { + "hp": 65, + "atk": 40, + "def": 70, + "spa": 80, + "spd": 140, + "spe": 70 + }, + "abilities": { + "0": "Swift Swim", + "1": "Water Absorb", + "H": "Water Veil" + }, + "heightm": 2.1, + "weightkg": 220, + "color": "Purple", + "prevo": "mantyke", + "evoLevel": 1, + "eggGroups": [ + "Water 1" + ] + }, + "skarmory": { + "num": 227, + "species": "Skarmory", + "types": [ + "Steel", + "Flying" + ], + "baseStats": { + "hp": 65, + "atk": 80, + "def": 140, + "spa": 40, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Keen Eye", + "1": "Sturdy", + "H": "Weak Armor" + }, + "heightm": 1.7, + "weightkg": 50.5, + "color": "Gray", + "eggGroups": [ + "Flying" + ] + }, + "houndour": { + "num": 228, + "species": "Houndour", + "types": [ + "Dark", + "Fire" + ], + "baseStats": { + "hp": 45, + "atk": 60, + "def": 30, + "spa": 80, + "spd": 50, + "spe": 65 + }, + "abilities": { + "0": "Early Bird", + "1": "Flash Fire", + "H": "Unnerve" + }, + "heightm": 0.6, + "weightkg": 10.8, + "color": "Black", + "evos": [ + "houndoom" + ], + "eggGroups": [ + "Field" + ] + }, + "houndoom": { + "num": 229, + "species": "Houndoom", + "types": [ + "Dark", + "Fire" + ], + "baseStats": { + "hp": 75, + "atk": 90, + "def": 50, + "spa": 110, + "spd": 80, + "spe": 95 + }, + "abilities": { + "0": "Early Bird", + "1": "Flash Fire", + "H": "Unnerve" + }, + "heightm": 1.4, + "weightkg": 35, + "color": "Black", + "prevo": "houndour", + "evoLevel": 24, + "eggGroups": [ + "Field" + ], + "otherFormes": [ + "houndoommega" + ] + }, + "houndoommega": { + "num": 229, + "species": "Houndoom-Mega", + "baseSpecies": "Houndoom", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dark", + "Fire" + ], + "baseStats": { + "hp": 75, + "atk": 90, + "def": 90, + "spa": 140, + "spd": 90, + "spe": 115 + }, + "abilities": { + "0": "Solar Power" + }, + "heightm": 1.9, + "weightkg": 49.5, + "color": "Black", + "eggGroups": [ + "Field" + ] + }, + "kingdra": { + "num": 230, + "species": "Kingdra", + "types": [ + "Water", + "Dragon" + ], + "baseStats": { + "hp": 75, + "atk": 95, + "def": 95, + "spa": 95, + "spd": 95, + "spe": 85 + }, + "abilities": { + "0": "Swift Swim", + "1": "Sniper", + "H": "Damp" + }, + "heightm": 1.8, + "weightkg": 152, + "color": "Blue", + "prevo": "seadra", + "evoLevel": 32, + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "phanpy": { + "num": 231, + "species": "Phanpy", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 90, + "atk": 60, + "def": 60, + "spa": 40, + "spd": 40, + "spe": 40 + }, + "abilities": { + "0": "Pickup", + "H": "Sand Veil" + }, + "heightm": 0.5, + "weightkg": 33.5, + "color": "Blue", + "evos": [ + "donphan" + ], + "eggGroups": [ + "Field" + ] + }, + "donphan": { + "num": 232, + "species": "Donphan", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 90, + "atk": 120, + "def": 120, + "spa": 60, + "spd": 60, + "spe": 50 + }, + "abilities": { + "0": "Sturdy", + "H": "Sand Veil" + }, + "heightm": 1.1, + "weightkg": 120, + "color": "Gray", + "prevo": "phanpy", + "evoLevel": 25, + "eggGroups": [ + "Field" + ] + }, + "porygon2": { + "num": 233, + "species": "Porygon2", + "types": [ + "Normal" + ], + "gender": "N", + "baseStats": { + "hp": 85, + "atk": 80, + "def": 90, + "spa": 105, + "spd": 95, + "spe": 60 + }, + "abilities": { + "0": "Trace", + "1": "Download", + "H": "Analytic" + }, + "heightm": 0.6, + "weightkg": 32.5, + "color": "Red", + "prevo": "porygon", + "evos": [ + "porygonz" + ], + "evoLevel": 1, + "eggGroups": [ + "Mineral" + ] + }, + "stantler": { + "num": 234, + "species": "Stantler", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 73, + "atk": 95, + "def": 62, + "spa": 85, + "spd": 65, + "spe": 85 + }, + "abilities": { + "0": "Intimidate", + "1": "Frisk", + "H": "Sap Sipper" + }, + "heightm": 1.4, + "weightkg": 71.2, + "color": "Brown", + "eggGroups": [ + "Field" + ] + }, + "smeargle": { + "num": 235, + "species": "Smeargle", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 55, + "atk": 20, + "def": 35, + "spa": 20, + "spd": 45, + "spe": 75 + }, + "abilities": { + "0": "Own Tempo", + "1": "Technician", + "H": "Moody" + }, + "heightm": 1.2, + "weightkg": 58, + "color": "White", + "eggGroups": [ + "Field" + ] + }, + "tyrogue": { + "num": 236, + "species": "Tyrogue", + "types": [ + "Fighting" + ], + "gender": "M", + "baseStats": { + "hp": 35, + "atk": 35, + "def": 35, + "spa": 35, + "spd": 35, + "spe": 35 + }, + "abilities": { + "0": "Guts", + "1": "Steadfast", + "H": "Vital Spirit" + }, + "heightm": 0.7, + "weightkg": 21, + "color": "Purple", + "evos": [ + "hitmonlee", + "hitmonchan", + "hitmontop" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "hitmontop": { + "num": 237, + "species": "Hitmontop", + "types": [ + "Fighting" + ], + "gender": "M", + "baseStats": { + "hp": 50, + "atk": 95, + "def": 95, + "spa": 35, + "spd": 110, + "spe": 70 + }, + "abilities": { + "0": "Intimidate", + "1": "Technician", + "H": "Steadfast" + }, + "heightm": 1.4, + "weightkg": 48, + "color": "Brown", + "prevo": "tyrogue", + "evoLevel": 20, + "eggGroups": [ + "Human-Like" + ] + }, + "smoochum": { + "num": 238, + "species": "Smoochum", + "types": [ + "Ice", + "Psychic" + ], + "gender": "F", + "baseStats": { + "hp": 45, + "atk": 30, + "def": 15, + "spa": 85, + "spd": 65, + "spe": 65 + }, + "abilities": { + "0": "Oblivious", + "1": "Forewarn", + "H": "Hydration" + }, + "heightm": 0.4, + "weightkg": 6, + "color": "Pink", + "evos": [ + "jynx" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "elekid": { + "num": 239, + "species": "Elekid", + "types": [ + "Electric" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 45, + "atk": 63, + "def": 37, + "spa": 65, + "spd": 55, + "spe": 95 + }, + "abilities": { + "0": "Static", + "H": "Vital Spirit" + }, + "heightm": 0.6, + "weightkg": 23.5, + "color": "Yellow", + "evos": [ + "electabuzz" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "magby": { + "num": 240, + "species": "Magby", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 45, + "atk": 75, + "def": 37, + "spa": 70, + "spd": 55, + "spe": 83 + }, + "abilities": { + "0": "Flame Body", + "H": "Vital Spirit" + }, + "heightm": 0.7, + "weightkg": 21.4, + "color": "Red", + "evos": [ + "magmar" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "miltank": { + "num": 241, + "species": "Miltank", + "types": [ + "Normal" + ], + "gender": "F", + "baseStats": { + "hp": 95, + "atk": 80, + "def": 105, + "spa": 40, + "spd": 70, + "spe": 100 + }, + "abilities": { + "0": "Thick Fat", + "1": "Scrappy", + "H": "Sap Sipper" + }, + "heightm": 1.2, + "weightkg": 75.5, + "color": "Pink", + "eggGroups": [ + "Field" + ] + }, + "blissey": { + "num": 242, + "species": "Blissey", + "types": [ + "Normal" + ], + "gender": "F", + "baseStats": { + "hp": 255, + "atk": 10, + "def": 10, + "spa": 75, + "spd": 135, + "spe": 55 + }, + "abilities": { + "0": "Natural Cure", + "1": "Serene Grace", + "H": "Healer" + }, + "heightm": 1.5, + "weightkg": 46.8, + "color": "Pink", + "prevo": "chansey", + "evoLevel": 2, + "eggGroups": [ + "Fairy" + ] + }, + "raikou": { + "num": 243, + "species": "Raikou", + "types": [ + "Electric" + ], + "gender": "N", + "baseStats": { + "hp": 90, + "atk": 85, + "def": 75, + "spa": 115, + "spd": 100, + "spe": 115 + }, + "abilities": { + "0": "Pressure", + "H": "Volt Absorb" + }, + "heightm": 1.9, + "weightkg": 178, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "entei": { + "num": 244, + "species": "Entei", + "types": [ + "Fire" + ], + "gender": "N", + "baseStats": { + "hp": 115, + "atk": 115, + "def": 85, + "spa": 90, + "spd": 75, + "spe": 100 + }, + "abilities": { + "0": "Pressure", + "H": "Flash Fire" + }, + "heightm": 2.1, + "weightkg": 198, + "color": "Brown", + "eggGroups": [ + "Undiscovered" + ] + }, + "suicune": { + "num": 245, + "species": "Suicune", + "types": [ + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 75, + "def": 115, + "spa": 90, + "spd": 115, + "spe": 85 + }, + "abilities": { + "0": "Pressure", + "H": "Water Absorb" + }, + "heightm": 2, + "weightkg": 187, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "larvitar": { + "num": 246, + "species": "Larvitar", + "types": [ + "Rock", + "Ground" + ], + "baseStats": { + "hp": 50, + "atk": 64, + "def": 50, + "spa": 45, + "spd": 50, + "spe": 41 + }, + "abilities": { + "0": "Guts", + "H": "Sand Veil" + }, + "heightm": 0.6, + "weightkg": 72, + "color": "Green", + "evos": [ + "pupitar" + ], + "eggGroups": [ + "Monster" + ] + }, + "pupitar": { + "num": 247, + "species": "Pupitar", + "types": [ + "Rock", + "Ground" + ], + "baseStats": { + "hp": 70, + "atk": 84, + "def": 70, + "spa": 65, + "spd": 70, + "spe": 51 + }, + "abilities": { + "0": "Shed Skin" + }, + "heightm": 1.2, + "weightkg": 152, + "color": "Gray", + "prevo": "larvitar", + "evos": [ + "tyranitar" + ], + "evoLevel": 30, + "eggGroups": [ + "Monster" + ] + }, + "tyranitar": { + "num": 248, + "species": "Tyranitar", + "types": [ + "Rock", + "Dark" + ], + "baseStats": { + "hp": 100, + "atk": 134, + "def": 110, + "spa": 95, + "spd": 100, + "spe": 61 + }, + "abilities": { + "0": "Sand Stream", + "H": "Unnerve" + }, + "heightm": 2, + "weightkg": 202, + "color": "Green", + "prevo": "pupitar", + "evoLevel": 55, + "eggGroups": [ + "Monster" + ], + "otherFormes": [ + "tyranitarmega" + ] + }, + "tyranitarmega": { + "num": 248, + "species": "Tyranitar-Mega", + "baseSpecies": "Tyranitar", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Rock", + "Dark" + ], + "baseStats": { + "hp": 100, + "atk": 164, + "def": 150, + "spa": 95, + "spd": 120, + "spe": 71 + }, + "abilities": { + "0": "Sand Stream" + }, + "heightm": 2.5, + "weightkg": 255, + "color": "Green", + "eggGroups": [ + "Monster" + ] + }, + "lugia": { + "num": 249, + "species": "Lugia", + "types": [ + "Psychic", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 106, + "atk": 90, + "def": 130, + "spa": 90, + "spd": 154, + "spe": 110 + }, + "abilities": { + "0": "Pressure", + "H": "Multiscale" + }, + "heightm": 5.2, + "weightkg": 216, + "color": "White", + "eggGroups": [ + "Undiscovered" + ] + }, + "hooh": { + "num": 250, + "species": "Ho-Oh", + "types": [ + "Fire", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 106, + "atk": 130, + "def": 90, + "spa": 110, + "spd": 154, + "spe": 90 + }, + "abilities": { + "0": "Pressure", + "H": "Regenerator" + }, + "heightm": 3.8, + "weightkg": 199, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ] + }, + "celebi": { + "num": 251, + "species": "Celebi", + "types": [ + "Psychic", + "Grass" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 100, + "def": 100, + "spa": 100, + "spd": 100, + "spe": 100 + }, + "abilities": { + "0": "Natural Cure" + }, + "heightm": 0.6, + "weightkg": 5, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ] + }, + "treecko": { + "num": 252, + "species": "Treecko", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 40, + "atk": 45, + "def": 35, + "spa": 65, + "spd": 55, + "spe": 70 + }, + "abilities": { + "0": "Overgrow", + "H": "Unburden" + }, + "heightm": 0.5, + "weightkg": 5, + "color": "Green", + "evos": [ + "grovyle" + ], + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "grovyle": { + "num": 253, + "species": "Grovyle", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 50, + "atk": 65, + "def": 45, + "spa": 85, + "spd": 65, + "spe": 95 + }, + "abilities": { + "0": "Overgrow", + "H": "Unburden" + }, + "heightm": 0.9, + "weightkg": 21.6, + "color": "Green", + "prevo": "treecko", + "evos": [ + "sceptile" + ], + "evoLevel": 16, + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "sceptile": { + "num": 254, + "species": "Sceptile", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 70, + "atk": 85, + "def": 65, + "spa": 105, + "spd": 85, + "spe": 120 + }, + "abilities": { + "0": "Overgrow", + "H": "Unburden" + }, + "heightm": 1.7, + "weightkg": 52.2, + "color": "Green", + "prevo": "grovyle", + "evoLevel": 36, + "eggGroups": [ + "Monster", + "Dragon" + ], + "otherFormes": [ + "sceptilemega" + ] + }, + "sceptilemega": { + "num": 254, + "species": "Sceptile-Mega", + "baseSpecies": "Sceptile", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Grass", + "Dragon" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 70, + "atk": 110, + "def": 75, + "spa": 145, + "spd": 85, + "spe": 145 + }, + "abilities": { + "0": "Lightning Rod" + }, + "heightm": 1.9, + "weightkg": 55.2, + "color": "Green", + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "torchic": { + "num": 255, + "species": "Torchic", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 45, + "atk": 60, + "def": 40, + "spa": 70, + "spd": 50, + "spe": 45 + }, + "abilities": { + "0": "Blaze", + "H": "Speed Boost" + }, + "heightm": 0.4, + "weightkg": 2.5, + "color": "Red", + "evos": [ + "combusken" + ], + "eggGroups": [ + "Field" + ] + }, + "combusken": { + "num": 256, + "species": "Combusken", + "types": [ + "Fire", + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 60, + "atk": 85, + "def": 60, + "spa": 85, + "spd": 60, + "spe": 55 + }, + "abilities": { + "0": "Blaze", + "H": "Speed Boost" + }, + "heightm": 0.9, + "weightkg": 19.5, + "color": "Red", + "prevo": "torchic", + "evos": [ + "blaziken" + ], + "evoLevel": 16, + "eggGroups": [ + "Field" + ] + }, + "blaziken": { + "num": 257, + "species": "Blaziken", + "types": [ + "Fire", + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 80, + "atk": 120, + "def": 70, + "spa": 110, + "spd": 70, + "spe": 80 + }, + "abilities": { + "0": "Blaze", + "H": "Speed Boost" + }, + "heightm": 1.9, + "weightkg": 52, + "color": "Red", + "prevo": "combusken", + "evoLevel": 36, + "eggGroups": [ + "Field" + ], + "otherFormes": [ + "blazikenmega" + ] + }, + "blazikenmega": { + "num": 257, + "species": "Blaziken-Mega", + "baseSpecies": "Blaziken", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Fire", + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 80, + "atk": 160, + "def": 80, + "spa": 130, + "spd": 80, + "spe": 100 + }, + "abilities": { + "0": "Speed Boost" + }, + "heightm": 1.9, + "weightkg": 52, + "color": "Red", + "eggGroups": [ + "Field" + ] + }, + "mudkip": { + "num": 258, + "species": "Mudkip", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 50, + "atk": 70, + "def": 50, + "spa": 50, + "spd": 50, + "spe": 40 + }, + "abilities": { + "0": "Torrent", + "H": "Damp" + }, + "heightm": 0.4, + "weightkg": 7.6, + "color": "Blue", + "evos": [ + "marshtomp" + ], + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "marshtomp": { + "num": 259, + "species": "Marshtomp", + "types": [ + "Water", + "Ground" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 70, + "atk": 85, + "def": 70, + "spa": 60, + "spd": 70, + "spe": 50 + }, + "abilities": { + "0": "Torrent", + "H": "Damp" + }, + "heightm": 0.7, + "weightkg": 28, + "color": "Blue", + "prevo": "mudkip", + "evos": [ + "swampert" + ], + "evoLevel": 16, + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "swampert": { + "num": 260, + "species": "Swampert", + "types": [ + "Water", + "Ground" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 100, + "atk": 110, + "def": 90, + "spa": 85, + "spd": 90, + "spe": 60 + }, + "abilities": { + "0": "Torrent", + "H": "Damp" + }, + "heightm": 1.5, + "weightkg": 81.9, + "color": "Blue", + "prevo": "marshtomp", + "evoLevel": 36, + "eggGroups": [ + "Monster", + "Water 1" + ], + "otherFormes": [ + "swampertmega" + ] + }, + "swampertmega": { + "num": 260, + "species": "Swampert-Mega", + "baseSpecies": "Swampert", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Water", + "Ground" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 100, + "atk": 150, + "def": 110, + "spa": 95, + "spd": 110, + "spe": 70 + }, + "abilities": { + "0": "Swift Swim" + }, + "heightm": 1.9, + "weightkg": 102, + "color": "Blue", + "eggGroups": [ + "Monster", + "Water 1" + ] + }, + "poochyena": { + "num": 261, + "species": "Poochyena", + "types": [ + "Dark" + ], + "baseStats": { + "hp": 35, + "atk": 55, + "def": 35, + "spa": 30, + "spd": 30, + "spe": 35 + }, + "abilities": { + "0": "Run Away", + "1": "Quick Feet", + "H": "Rattled" + }, + "heightm": 0.5, + "weightkg": 13.6, + "color": "Gray", + "evos": [ + "mightyena" + ], + "eggGroups": [ + "Field" + ] + }, + "mightyena": { + "num": 262, + "species": "Mightyena", + "types": [ + "Dark" + ], + "baseStats": { + "hp": 70, + "atk": 90, + "def": 70, + "spa": 60, + "spd": 60, + "spe": 70 + }, + "abilities": { + "0": "Intimidate", + "1": "Quick Feet", + "H": "Moxie" + }, + "heightm": 1, + "weightkg": 37, + "color": "Gray", + "prevo": "poochyena", + "evoLevel": 18, + "eggGroups": [ + "Field" + ] + }, + "zigzagoon": { + "num": 263, + "species": "Zigzagoon", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 38, + "atk": 30, + "def": 41, + "spa": 30, + "spd": 41, + "spe": 60 + }, + "abilities": { + "0": "Pickup", + "1": "Gluttony", + "H": "Quick Feet" + }, + "heightm": 0.4, + "weightkg": 17.5, + "color": "Brown", + "evos": [ + "linoone" + ], + "eggGroups": [ + "Field" + ] + }, + "linoone": { + "num": 264, + "species": "Linoone", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 78, + "atk": 70, + "def": 61, + "spa": 50, + "spd": 61, + "spe": 100 + }, + "abilities": { + "0": "Pickup", + "1": "Gluttony", + "H": "Quick Feet" + }, + "heightm": 0.5, + "weightkg": 32.5, + "color": "White", + "prevo": "zigzagoon", + "evoLevel": 20, + "eggGroups": [ + "Field" + ] + }, + "wurmple": { + "num": 265, + "species": "Wurmple", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 45, + "atk": 45, + "def": 35, + "spa": 20, + "spd": 30, + "spe": 20 + }, + "abilities": { + "0": "Shield Dust", + "H": "Run Away" + }, + "heightm": 0.3, + "weightkg": 3.6, + "color": "Red", + "evos": [ + "silcoon", + "cascoon" + ], + "eggGroups": [ + "Bug" + ] + }, + "silcoon": { + "num": 266, + "species": "Silcoon", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 50, + "atk": 35, + "def": 55, + "spa": 25, + "spd": 25, + "spe": 15 + }, + "abilities": { + "0": "Shed Skin" + }, + "heightm": 0.6, + "weightkg": 10, + "color": "White", + "prevo": "wurmple", + "evos": [ + "beautifly" + ], + "evoLevel": 7, + "eggGroups": [ + "Bug" + ] + }, + "beautifly": { + "num": 267, + "species": "Beautifly", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 60, + "atk": 70, + "def": 50, + "spa": 100, + "spd": 50, + "spe": 65 + }, + "abilities": { + "0": "Swarm", + "H": "Rivalry" + }, + "heightm": 1, + "weightkg": 28.4, + "color": "Yellow", + "prevo": "silcoon", + "evoLevel": 10, + "eggGroups": [ + "Bug" + ] + }, + "cascoon": { + "num": 268, + "species": "Cascoon", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 50, + "atk": 35, + "def": 55, + "spa": 25, + "spd": 25, + "spe": 15 + }, + "abilities": { + "0": "Shed Skin" + }, + "heightm": 0.7, + "weightkg": 11.5, + "color": "Purple", + "prevo": "wurmple", + "evos": [ + "dustox" + ], + "evoLevel": 7, + "eggGroups": [ + "Bug" + ] + }, + "dustox": { + "num": 269, + "species": "Dustox", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 50, + "def": 70, + "spa": 50, + "spd": 90, + "spe": 65 + }, + "abilities": { + "0": "Shield Dust", + "H": "Compound Eyes" + }, + "heightm": 1.2, + "weightkg": 31.6, + "color": "Green", + "prevo": "cascoon", + "evoLevel": 10, + "eggGroups": [ + "Bug" + ] + }, + "lotad": { + "num": 270, + "species": "Lotad", + "types": [ + "Water", + "Grass" + ], + "baseStats": { + "hp": 40, + "atk": 30, + "def": 30, + "spa": 40, + "spd": 50, + "spe": 30 + }, + "abilities": { + "0": "Swift Swim", + "1": "Rain Dish", + "H": "Own Tempo" + }, + "heightm": 0.5, + "weightkg": 2.6, + "color": "Green", + "evos": [ + "lombre" + ], + "eggGroups": [ + "Water 1", + "Grass" + ] + }, + "lombre": { + "num": 271, + "species": "Lombre", + "types": [ + "Water", + "Grass" + ], + "baseStats": { + "hp": 60, + "atk": 50, + "def": 50, + "spa": 60, + "spd": 70, + "spe": 50 + }, + "abilities": { + "0": "Swift Swim", + "1": "Rain Dish", + "H": "Own Tempo" + }, + "heightm": 1.2, + "weightkg": 32.5, + "color": "Green", + "prevo": "lotad", + "evos": [ + "ludicolo" + ], + "evoLevel": 14, + "eggGroups": [ + "Water 1", + "Grass" + ] + }, + "ludicolo": { + "num": 272, + "species": "Ludicolo", + "types": [ + "Water", + "Grass" + ], + "baseStats": { + "hp": 80, + "atk": 70, + "def": 70, + "spa": 90, + "spd": 100, + "spe": 70 + }, + "abilities": { + "0": "Swift Swim", + "1": "Rain Dish", + "H": "Own Tempo" + }, + "heightm": 1.5, + "weightkg": 55, + "color": "Green", + "prevo": "lombre", + "evoLevel": 14, + "eggGroups": [ + "Water 1", + "Grass" + ] + }, + "seedot": { + "num": 273, + "species": "Seedot", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 40, + "atk": 40, + "def": 50, + "spa": 30, + "spd": 30, + "spe": 30 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Early Bird", + "H": "Pickpocket" + }, + "heightm": 0.5, + "weightkg": 4, + "color": "Brown", + "evos": [ + "nuzleaf" + ], + "eggGroups": [ + "Field", + "Grass" + ] + }, + "nuzleaf": { + "num": 274, + "species": "Nuzleaf", + "types": [ + "Grass", + "Dark" + ], + "baseStats": { + "hp": 70, + "atk": 70, + "def": 40, + "spa": 60, + "spd": 40, + "spe": 60 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Early Bird", + "H": "Pickpocket" + }, + "heightm": 1, + "weightkg": 28, + "color": "Brown", + "prevo": "seedot", + "evos": [ + "shiftry" + ], + "evoLevel": 14, + "eggGroups": [ + "Field", + "Grass" + ] + }, + "shiftry": { + "num": 275, + "species": "Shiftry", + "types": [ + "Grass", + "Dark" + ], + "baseStats": { + "hp": 90, + "atk": 100, + "def": 60, + "spa": 90, + "spd": 60, + "spe": 80 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Early Bird", + "H": "Pickpocket" + }, + "heightm": 1.3, + "weightkg": 59.6, + "color": "Brown", + "prevo": "nuzleaf", + "evoLevel": 14, + "eggGroups": [ + "Field", + "Grass" + ] + }, + "taillow": { + "num": 276, + "species": "Taillow", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 40, + "atk": 55, + "def": 30, + "spa": 30, + "spd": 30, + "spe": 85 + }, + "abilities": { + "0": "Guts", + "H": "Scrappy" + }, + "heightm": 0.3, + "weightkg": 2.3, + "color": "Blue", + "evos": [ + "swellow" + ], + "eggGroups": [ + "Flying" + ] + }, + "swellow": { + "num": 277, + "species": "Swellow", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 60, + "atk": 85, + "def": 60, + "spa": 50, + "spd": 50, + "spe": 125 + }, + "abilities": { + "0": "Guts", + "H": "Scrappy" + }, + "heightm": 0.7, + "weightkg": 19.8, + "color": "Blue", + "prevo": "taillow", + "evoLevel": 22, + "eggGroups": [ + "Flying" + ] + }, + "wingull": { + "num": 278, + "species": "Wingull", + "types": [ + "Water", + "Flying" + ], + "baseStats": { + "hp": 40, + "atk": 30, + "def": 30, + "spa": 55, + "spd": 30, + "spe": 85 + }, + "abilities": { + "0": "Keen Eye", + "H": "Rain Dish" + }, + "heightm": 0.6, + "weightkg": 9.5, + "color": "White", + "evos": [ + "pelipper" + ], + "eggGroups": [ + "Water 1", + "Flying" + ] + }, + "pelipper": { + "num": 279, + "species": "Pelipper", + "types": [ + "Water", + "Flying" + ], + "baseStats": { + "hp": 60, + "atk": 50, + "def": 100, + "spa": 85, + "spd": 70, + "spe": 65 + }, + "abilities": { + "0": "Keen Eye", + "H": "Rain Dish" + }, + "heightm": 1.2, + "weightkg": 28, + "color": "Yellow", + "prevo": "wingull", + "evoLevel": 25, + "eggGroups": [ + "Water 1", + "Flying" + ] + }, + "ralts": { + "num": 280, + "species": "Ralts", + "types": [ + "Psychic", + "Fairy" + ], + "baseStats": { + "hp": 28, + "atk": 25, + "def": 25, + "spa": 45, + "spd": 35, + "spe": 40 + }, + "abilities": { + "0": "Synchronize", + "1": "Trace", + "H": "Telepathy" + }, + "heightm": 0.4, + "weightkg": 6.6, + "color": "White", + "evos": [ + "kirlia" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "kirlia": { + "num": 281, + "species": "Kirlia", + "types": [ + "Psychic", + "Fairy" + ], + "baseStats": { + "hp": 38, + "atk": 35, + "def": 35, + "spa": 65, + "spd": 55, + "spe": 50 + }, + "abilities": { + "0": "Synchronize", + "1": "Trace", + "H": "Telepathy" + }, + "heightm": 0.8, + "weightkg": 20.2, + "color": "White", + "prevo": "ralts", + "evos": [ + "gardevoir", + "gallade" + ], + "evoLevel": 20, + "eggGroups": [ + "Amorphous" + ] + }, + "gardevoir": { + "num": 282, + "species": "Gardevoir", + "types": [ + "Psychic", + "Fairy" + ], + "baseStats": { + "hp": 68, + "atk": 65, + "def": 65, + "spa": 125, + "spd": 115, + "spe": 80 + }, + "abilities": { + "0": "Synchronize", + "1": "Trace", + "H": "Telepathy" + }, + "heightm": 1.6, + "weightkg": 48.4, + "color": "White", + "prevo": "kirlia", + "evoLevel": 30, + "eggGroups": [ + "Amorphous" + ], + "otherFormes": [ + "gardevoirmega" + ] + }, + "gardevoirmega": { + "num": 282, + "species": "Gardevoir-Mega", + "baseSpecies": "Gardevoir", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Psychic", + "Fairy" + ], + "baseStats": { + "hp": 68, + "atk": 85, + "def": 65, + "spa": 165, + "spd": 135, + "spe": 100 + }, + "abilities": { + "0": "Pixilate" + }, + "heightm": 1.6, + "weightkg": 48.4, + "color": "White", + "eggGroups": [ + "Amorphous" + ] + }, + "surskit": { + "num": 283, + "species": "Surskit", + "types": [ + "Bug", + "Water" + ], + "baseStats": { + "hp": 40, + "atk": 30, + "def": 32, + "spa": 50, + "spd": 52, + "spe": 65 + }, + "abilities": { + "0": "Swift Swim", + "H": "Rain Dish" + }, + "heightm": 0.5, + "weightkg": 1.7, + "color": "Blue", + "evos": [ + "masquerain" + ], + "eggGroups": [ + "Water 1", + "Bug" + ] + }, + "masquerain": { + "num": 284, + "species": "Masquerain", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 70, + "atk": 60, + "def": 62, + "spa": 80, + "spd": 82, + "spe": 60 + }, + "abilities": { + "0": "Intimidate", + "H": "Unnerve" + }, + "heightm": 0.8, + "weightkg": 3.6, + "color": "Blue", + "prevo": "surskit", + "evoLevel": 22, + "eggGroups": [ + "Water 1", + "Bug" + ] + }, + "shroomish": { + "num": 285, + "species": "Shroomish", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 60, + "atk": 40, + "def": 60, + "spa": 40, + "spd": 60, + "spe": 35 + }, + "abilities": { + "0": "Effect Spore", + "1": "Poison Heal", + "H": "Quick Feet" + }, + "heightm": 0.4, + "weightkg": 4.5, + "color": "Brown", + "evos": [ + "breloom" + ], + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "breloom": { + "num": 286, + "species": "Breloom", + "types": [ + "Grass", + "Fighting" + ], + "baseStats": { + "hp": 60, + "atk": 130, + "def": 80, + "spa": 60, + "spd": 60, + "spe": 70 + }, + "abilities": { + "0": "Effect Spore", + "1": "Poison Heal", + "H": "Technician" + }, + "heightm": 1.2, + "weightkg": 39.2, + "color": "Green", + "prevo": "shroomish", + "evoLevel": 23, + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "slakoth": { + "num": 287, + "species": "Slakoth", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 60, + "atk": 60, + "def": 60, + "spa": 35, + "spd": 35, + "spe": 30 + }, + "abilities": { + "0": "Truant" + }, + "heightm": 0.8, + "weightkg": 24, + "color": "Brown", + "evos": [ + "vigoroth" + ], + "eggGroups": [ + "Field" + ] + }, + "vigoroth": { + "num": 288, + "species": "Vigoroth", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 80, + "atk": 80, + "def": 80, + "spa": 55, + "spd": 55, + "spe": 90 + }, + "abilities": { + "0": "Vital Spirit" + }, + "heightm": 1.4, + "weightkg": 46.5, + "color": "White", + "prevo": "slakoth", + "evos": [ + "slaking" + ], + "evoLevel": 18, + "eggGroups": [ + "Field" + ] + }, + "slaking": { + "num": 289, + "species": "Slaking", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 150, + "atk": 160, + "def": 100, + "spa": 95, + "spd": 65, + "spe": 100 + }, + "abilities": { + "0": "Truant" + }, + "heightm": 2, + "weightkg": 130.5, + "color": "Brown", + "prevo": "vigoroth", + "evoLevel": 36, + "eggGroups": [ + "Field" + ] + }, + "nincada": { + "num": 290, + "species": "Nincada", + "types": [ + "Bug", + "Ground" + ], + "baseStats": { + "hp": 31, + "atk": 45, + "def": 90, + "spa": 30, + "spd": 30, + "spe": 40 + }, + "abilities": { + "0": "Compound Eyes", + "H": "Run Away" + }, + "heightm": 0.5, + "weightkg": 5.5, + "color": "Gray", + "evos": [ + "ninjask", + "shedinja" + ], + "eggGroups": [ + "Bug" + ] + }, + "ninjask": { + "num": 291, + "species": "Ninjask", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 61, + "atk": 90, + "def": 45, + "spa": 50, + "spd": 50, + "spe": 160 + }, + "abilities": { + "0": "Speed Boost", + "H": "Infiltrator" + }, + "heightm": 0.8, + "weightkg": 12, + "color": "Yellow", + "prevo": "nincada", + "evoLevel": 20, + "eggGroups": [ + "Bug" + ] + }, + "shedinja": { + "num": 292, + "species": "Shedinja", + "types": [ + "Bug", + "Ghost" + ], + "gender": "N", + "baseStats": { + "hp": 1, + "atk": 90, + "def": 45, + "spa": 30, + "spd": 30, + "spe": 40 + }, + "maxHP": 1, + "abilities": { + "0": "Wonder Guard" + }, + "heightm": 0.8, + "weightkg": 1.2, + "color": "Brown", + "prevo": "nincada", + "evoLevel": 20, + "eggGroups": [ + "Mineral" + ] + }, + "whismur": { + "num": 293, + "species": "Whismur", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 64, + "atk": 51, + "def": 23, + "spa": 51, + "spd": 23, + "spe": 28 + }, + "abilities": { + "0": "Soundproof", + "H": "Rattled" + }, + "heightm": 0.6, + "weightkg": 16.3, + "color": "Pink", + "evos": [ + "loudred" + ], + "eggGroups": [ + "Monster", + "Field" + ] + }, + "loudred": { + "num": 294, + "species": "Loudred", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 84, + "atk": 71, + "def": 43, + "spa": 71, + "spd": 43, + "spe": 48 + }, + "abilities": { + "0": "Soundproof", + "H": "Scrappy" + }, + "heightm": 1, + "weightkg": 40.5, + "color": "Blue", + "prevo": "whismur", + "evos": [ + "exploud" + ], + "evoLevel": 20, + "eggGroups": [ + "Monster", + "Field" + ] + }, + "exploud": { + "num": 295, + "species": "Exploud", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 104, + "atk": 91, + "def": 63, + "spa": 91, + "spd": 73, + "spe": 68 + }, + "abilities": { + "0": "Soundproof", + "H": "Scrappy" + }, + "heightm": 1.5, + "weightkg": 84, + "color": "Blue", + "prevo": "loudred", + "evoLevel": 40, + "eggGroups": [ + "Monster", + "Field" + ] + }, + "makuhita": { + "num": 296, + "species": "Makuhita", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 72, + "atk": 60, + "def": 30, + "spa": 20, + "spd": 30, + "spe": 25 + }, + "abilities": { + "0": "Thick Fat", + "1": "Guts", + "H": "Sheer Force" + }, + "heightm": 1, + "weightkg": 86.4, + "color": "Yellow", + "evos": [ + "hariyama" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "hariyama": { + "num": 297, + "species": "Hariyama", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 144, + "atk": 120, + "def": 60, + "spa": 40, + "spd": 60, + "spe": 50 + }, + "abilities": { + "0": "Thick Fat", + "1": "Guts", + "H": "Sheer Force" + }, + "heightm": 2.3, + "weightkg": 253.8, + "color": "Brown", + "prevo": "makuhita", + "evoLevel": 24, + "eggGroups": [ + "Human-Like" + ] + }, + "azurill": { + "num": 298, + "species": "Azurill", + "types": [ + "Normal", + "Fairy" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 50, + "atk": 20, + "def": 40, + "spa": 20, + "spd": 40, + "spe": 20 + }, + "abilities": { + "0": "Thick Fat", + "1": "Huge Power", + "H": "Sap Sipper" + }, + "heightm": 0.2, + "weightkg": 2, + "color": "Blue", + "evos": [ + "marill" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "nosepass": { + "num": 299, + "species": "Nosepass", + "types": [ + "Rock" + ], + "baseStats": { + "hp": 30, + "atk": 45, + "def": 135, + "spa": 45, + "spd": 90, + "spe": 30 + }, + "abilities": { + "0": "Sturdy", + "1": "Magnet Pull", + "H": "Sand Force" + }, + "heightm": 1, + "weightkg": 97, + "color": "Gray", + "evos": [ + "probopass" + ], + "eggGroups": [ + "Mineral" + ] + }, + "skitty": { + "num": 300, + "species": "Skitty", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 50, + "atk": 45, + "def": 45, + "spa": 35, + "spd": 35, + "spe": 50 + }, + "abilities": { + "0": "Cute Charm", + "1": "Normalize", + "H": "Wonder Skin" + }, + "heightm": 0.6, + "weightkg": 11, + "color": "Pink", + "evos": [ + "delcatty" + ], + "eggGroups": [ + "Field", + "Fairy" + ] + }, + "delcatty": { + "num": 301, + "species": "Delcatty", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 70, + "atk": 65, + "def": 65, + "spa": 55, + "spd": 55, + "spe": 70 + }, + "abilities": { + "0": "Cute Charm", + "1": "Normalize", + "H": "Wonder Skin" + }, + "heightm": 1.1, + "weightkg": 32.6, + "color": "Purple", + "prevo": "skitty", + "evoLevel": 1, + "eggGroups": [ + "Field", + "Fairy" + ] + }, + "sableye": { + "num": 302, + "species": "Sableye", + "types": [ + "Dark", + "Ghost" + ], + "baseStats": { + "hp": 50, + "atk": 75, + "def": 75, + "spa": 65, + "spd": 65, + "spe": 50 + }, + "abilities": { + "0": "Keen Eye", + "1": "Stall", + "H": "Prankster" + }, + "heightm": 0.5, + "weightkg": 11, + "color": "Purple", + "eggGroups": [ + "Human-Like" + ], + "otherFormes": [ + "sableyemega" + ] + }, + "sableyemega": { + "num": 302, + "species": "Sableye-Mega", + "baseSpecies": "Sableye", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dark", + "Ghost" + ], + "baseStats": { + "hp": 50, + "atk": 85, + "def": 125, + "spa": 85, + "spd": 115, + "spe": 20 + }, + "abilities": { + "0": "Magic Bounce" + }, + "heightm": 0.5, + "weightkg": 161, + "color": "Purple", + "eggGroups": [ + "Human-Like" + ] + }, + "mawile": { + "num": 303, + "species": "Mawile", + "types": [ + "Steel", + "Fairy" + ], + "baseStats": { + "hp": 50, + "atk": 85, + "def": 85, + "spa": 55, + "spd": 55, + "spe": 50 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Intimidate", + "H": "Sheer Force" + }, + "heightm": 0.6, + "weightkg": 11.5, + "color": "Black", + "eggGroups": [ + "Field", + "Fairy" + ], + "otherFormes": [ + "mawilemega" + ] + }, + "mawilemega": { + "num": 303, + "species": "Mawile-Mega", + "baseSpecies": "Mawile", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Steel", + "Fairy" + ], + "baseStats": { + "hp": 50, + "atk": 105, + "def": 125, + "spa": 55, + "spd": 95, + "spe": 50 + }, + "abilities": { + "0": "Huge Power" + }, + "heightm": 1, + "weightkg": 23.5, + "color": "Black", + "eggGroups": [ + "Field", + "Fairy" + ] + }, + "aron": { + "num": 304, + "species": "Aron", + "types": [ + "Steel", + "Rock" + ], + "baseStats": { + "hp": 50, + "atk": 70, + "def": 100, + "spa": 40, + "spd": 40, + "spe": 30 + }, + "abilities": { + "0": "Sturdy", + "1": "Rock Head", + "H": "Heavy Metal" + }, + "heightm": 0.4, + "weightkg": 60, + "color": "Gray", + "evos": [ + "lairon" + ], + "eggGroups": [ + "Monster" + ] + }, + "lairon": { + "num": 305, + "species": "Lairon", + "types": [ + "Steel", + "Rock" + ], + "baseStats": { + "hp": 60, + "atk": 90, + "def": 140, + "spa": 50, + "spd": 50, + "spe": 40 + }, + "abilities": { + "0": "Sturdy", + "1": "Rock Head", + "H": "Heavy Metal" + }, + "heightm": 0.9, + "weightkg": 120, + "color": "Gray", + "prevo": "aron", + "evos": [ + "aggron" + ], + "evoLevel": 32, + "eggGroups": [ + "Monster" + ] + }, + "aggron": { + "num": 306, + "species": "Aggron", + "types": [ + "Steel", + "Rock" + ], + "baseStats": { + "hp": 70, + "atk": 110, + "def": 180, + "spa": 60, + "spd": 60, + "spe": 50 + }, + "abilities": { + "0": "Sturdy", + "1": "Rock Head", + "H": "Heavy Metal" + }, + "heightm": 2.1, + "weightkg": 360, + "color": "Gray", + "prevo": "lairon", + "evoLevel": 42, + "eggGroups": [ + "Monster" + ], + "otherFormes": [ + "aggronmega" + ] + }, + "aggronmega": { + "num": 306, + "species": "Aggron-Mega", + "baseSpecies": "Aggron", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Steel" + ], + "baseStats": { + "hp": 70, + "atk": 140, + "def": 230, + "spa": 60, + "spd": 80, + "spe": 50 + }, + "abilities": { + "0": "Filter" + }, + "heightm": 2.2, + "weightkg": 395, + "color": "Gray", + "eggGroups": [ + "Monster" + ] + }, + "meditite": { + "num": 307, + "species": "Meditite", + "types": [ + "Fighting", + "Psychic" + ], + "baseStats": { + "hp": 30, + "atk": 40, + "def": 55, + "spa": 40, + "spd": 55, + "spe": 60 + }, + "abilities": { + "0": "Pure Power", + "H": "Telepathy" + }, + "heightm": 0.6, + "weightkg": 11.2, + "color": "Blue", + "evos": [ + "medicham" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "medicham": { + "num": 308, + "species": "Medicham", + "types": [ + "Fighting", + "Psychic" + ], + "baseStats": { + "hp": 60, + "atk": 60, + "def": 75, + "spa": 60, + "spd": 75, + "spe": 80 + }, + "abilities": { + "0": "Pure Power", + "H": "Telepathy" + }, + "heightm": 1.3, + "weightkg": 31.5, + "color": "Red", + "prevo": "meditite", + "evoLevel": 37, + "eggGroups": [ + "Human-Like" + ], + "otherFormes": [ + "medichammega" + ] + }, + "medichammega": { + "num": 308, + "species": "Medicham-Mega", + "baseSpecies": "Medicham", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Fighting", + "Psychic" + ], + "baseStats": { + "hp": 60, + "atk": 100, + "def": 85, + "spa": 80, + "spd": 85, + "spe": 100 + }, + "abilities": { + "0": "Pure Power" + }, + "heightm": 1.3, + "weightkg": 31.5, + "color": "Red", + "eggGroups": [ + "Human-Like" + ] + }, + "electrike": { + "num": 309, + "species": "Electrike", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 40, + "atk": 45, + "def": 40, + "spa": 65, + "spd": 40, + "spe": 65 + }, + "abilities": { + "0": "Static", + "1": "Lightning Rod", + "H": "Minus" + }, + "heightm": 0.6, + "weightkg": 15.2, + "color": "Green", + "evos": [ + "manectric" + ], + "eggGroups": [ + "Field" + ] + }, + "manectric": { + "num": 310, + "species": "Manectric", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 70, + "atk": 75, + "def": 60, + "spa": 105, + "spd": 60, + "spe": 105 + }, + "abilities": { + "0": "Static", + "1": "Lightning Rod", + "H": "Minus" + }, + "heightm": 1.5, + "weightkg": 40.2, + "color": "Yellow", + "prevo": "electrike", + "evoLevel": 26, + "eggGroups": [ + "Field" + ], + "otherFormes": [ + "manectricmega" + ] + }, + "manectricmega": { + "num": 310, + "species": "Manectric-Mega", + "baseSpecies": "Manectric", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 70, + "atk": 75, + "def": 80, + "spa": 135, + "spd": 80, + "spe": 135 + }, + "abilities": { + "0": "Intimidate" + }, + "heightm": 1.8, + "weightkg": 44, + "color": "Yellow", + "eggGroups": [ + "Field" + ] + }, + "plusle": { + "num": 311, + "species": "Plusle", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 60, + "atk": 50, + "def": 40, + "spa": 85, + "spd": 75, + "spe": 95 + }, + "abilities": { + "0": "Plus", + "H": "Lightning Rod" + }, + "heightm": 0.4, + "weightkg": 4.2, + "color": "Yellow", + "eggGroups": [ + "Fairy" + ] + }, + "minun": { + "num": 312, + "species": "Minun", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 60, + "atk": 40, + "def": 50, + "spa": 75, + "spd": 85, + "spe": 95 + }, + "abilities": { + "0": "Minus", + "H": "Volt Absorb" + }, + "heightm": 0.4, + "weightkg": 4.2, + "color": "Yellow", + "eggGroups": [ + "Fairy" + ] + }, + "volbeat": { + "num": 313, + "species": "Volbeat", + "types": [ + "Bug" + ], + "gender": "M", + "baseStats": { + "hp": 65, + "atk": 73, + "def": 55, + "spa": 47, + "spd": 75, + "spe": 85 + }, + "abilities": { + "0": "Illuminate", + "1": "Swarm", + "H": "Prankster" + }, + "heightm": 0.7, + "weightkg": 17.7, + "color": "Gray", + "eggGroups": [ + "Bug", + "Human-Like" + ] + }, + "illumise": { + "num": 314, + "species": "Illumise", + "types": [ + "Bug" + ], + "gender": "F", + "baseStats": { + "hp": 65, + "atk": 47, + "def": 55, + "spa": 73, + "spd": 75, + "spe": 85 + }, + "abilities": { + "0": "Oblivious", + "1": "Tinted Lens", + "H": "Prankster" + }, + "heightm": 0.6, + "weightkg": 17.7, + "color": "Purple", + "eggGroups": [ + "Bug", + "Human-Like" + ] + }, + "roselia": { + "num": 315, + "species": "Roselia", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 50, + "atk": 60, + "def": 45, + "spa": 100, + "spd": 80, + "spe": 65 + }, + "abilities": { + "0": "Natural Cure", + "1": "Poison Point", + "H": "Leaf Guard" + }, + "heightm": 0.3, + "weightkg": 2, + "color": "Green", + "prevo": "budew", + "evos": [ + "roserade" + ], + "evoLevel": 1, + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "gulpin": { + "num": 316, + "species": "Gulpin", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 70, + "atk": 43, + "def": 53, + "spa": 43, + "spd": 53, + "spe": 40 + }, + "abilities": { + "0": "Liquid Ooze", + "1": "Sticky Hold", + "H": "Gluttony" + }, + "heightm": 0.4, + "weightkg": 10.3, + "color": "Green", + "evos": [ + "swalot" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "swalot": { + "num": 317, + "species": "Swalot", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 100, + "atk": 73, + "def": 83, + "spa": 73, + "spd": 83, + "spe": 55 + }, + "abilities": { + "0": "Liquid Ooze", + "1": "Sticky Hold", + "H": "Gluttony" + }, + "heightm": 1.7, + "weightkg": 80, + "color": "Purple", + "prevo": "gulpin", + "evoLevel": 26, + "eggGroups": [ + "Amorphous" + ] + }, + "carvanha": { + "num": 318, + "species": "Carvanha", + "types": [ + "Water", + "Dark" + ], + "baseStats": { + "hp": 45, + "atk": 90, + "def": 20, + "spa": 65, + "spd": 20, + "spe": 65 + }, + "abilities": { + "0": "Rough Skin", + "H": "Speed Boost" + }, + "heightm": 0.8, + "weightkg": 20.8, + "color": "Red", + "evos": [ + "sharpedo" + ], + "eggGroups": [ + "Water 2" + ] + }, + "sharpedo": { + "num": 319, + "species": "Sharpedo", + "types": [ + "Water", + "Dark" + ], + "baseStats": { + "hp": 70, + "atk": 120, + "def": 40, + "spa": 95, + "spd": 40, + "spe": 95 + }, + "abilities": { + "0": "Rough Skin", + "H": "Speed Boost" + }, + "heightm": 1.8, + "weightkg": 88.8, + "color": "Blue", + "prevo": "carvanha", + "evoLevel": 30, + "eggGroups": [ + "Water 2" + ], + "otherFormes": [ + "sharpedomega" + ] + }, + "sharpedomega": { + "num": 319, + "species": "Sharpedo-Mega", + "baseSpecies": "Sharpedo", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Water", + "Dark" + ], + "baseStats": { + "hp": 70, + "atk": 140, + "def": 70, + "spa": 110, + "spd": 65, + "spe": 105 + }, + "abilities": { + "0": "Strong Jaw" + }, + "heightm": 2.5, + "weightkg": 130.3, + "color": "Blue", + "eggGroups": [ + "Water 2" + ] + }, + "wailmer": { + "num": 320, + "species": "Wailmer", + "types": [ + "Water" + ], + "baseStats": { + "hp": 130, + "atk": 70, + "def": 35, + "spa": 70, + "spd": 35, + "spe": 60 + }, + "abilities": { + "0": "Water Veil", + "1": "Oblivious", + "H": "Pressure" + }, + "heightm": 2, + "weightkg": 130, + "color": "Blue", + "evos": [ + "wailord" + ], + "eggGroups": [ + "Field", + "Water 2" + ] + }, + "wailord": { + "num": 321, + "species": "Wailord", + "types": [ + "Water" + ], + "baseStats": { + "hp": 170, + "atk": 90, + "def": 45, + "spa": 90, + "spd": 45, + "spe": 60 + }, + "abilities": { + "0": "Water Veil", + "1": "Oblivious", + "H": "Pressure" + }, + "heightm": 14.5, + "weightkg": 398, + "color": "Blue", + "prevo": "wailmer", + "evoLevel": 40, + "eggGroups": [ + "Field", + "Water 2" + ] + }, + "numel": { + "num": 322, + "species": "Numel", + "types": [ + "Fire", + "Ground" + ], + "baseStats": { + "hp": 60, + "atk": 60, + "def": 40, + "spa": 65, + "spd": 45, + "spe": 35 + }, + "abilities": { + "0": "Oblivious", + "1": "Simple", + "H": "Own Tempo" + }, + "heightm": 0.7, + "weightkg": 24, + "color": "Yellow", + "evos": [ + "camerupt" + ], + "eggGroups": [ + "Field" + ] + }, + "camerupt": { + "num": 323, + "species": "Camerupt", + "types": [ + "Fire", + "Ground" + ], + "baseStats": { + "hp": 70, + "atk": 100, + "def": 70, + "spa": 105, + "spd": 75, + "spe": 40 + }, + "abilities": { + "0": "Magma Armor", + "1": "Solid Rock", + "H": "Anger Point" + }, + "heightm": 1.9, + "weightkg": 220, + "color": "Red", + "prevo": "numel", + "evoLevel": 33, + "eggGroups": [ + "Field" + ], + "otherFormes": [ + "cameruptmega" + ] + }, + "cameruptmega": { + "num": 323, + "species": "Camerupt-Mega", + "baseSpecies": "Camerupt", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Fire", + "Ground" + ], + "baseStats": { + "hp": 70, + "atk": 120, + "def": 100, + "spa": 145, + "spd": 105, + "spe": 20 + }, + "abilities": { + "0": "Sheer Force" + }, + "heightm": 2.5, + "weightkg": 320.5, + "color": "Red", + "eggGroups": [ + "Field" + ] + }, + "torkoal": { + "num": 324, + "species": "Torkoal", + "types": [ + "Fire" + ], + "baseStats": { + "hp": 70, + "atk": 85, + "def": 140, + "spa": 85, + "spd": 70, + "spe": 20 + }, + "abilities": { + "0": "White Smoke", + "H": "Shell Armor" + }, + "heightm": 0.5, + "weightkg": 80.4, + "color": "Brown", + "eggGroups": [ + "Field" + ] + }, + "spoink": { + "num": 325, + "species": "Spoink", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 60, + "atk": 25, + "def": 35, + "spa": 70, + "spd": 80, + "spe": 60 + }, + "abilities": { + "0": "Thick Fat", + "1": "Own Tempo", + "H": "Gluttony" + }, + "heightm": 0.7, + "weightkg": 30.6, + "color": "Black", + "evos": [ + "grumpig" + ], + "eggGroups": [ + "Field" + ] + }, + "grumpig": { + "num": 326, + "species": "Grumpig", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 80, + "atk": 45, + "def": 65, + "spa": 90, + "spd": 110, + "spe": 80 + }, + "abilities": { + "0": "Thick Fat", + "1": "Own Tempo", + "H": "Gluttony" + }, + "heightm": 0.9, + "weightkg": 71.5, + "color": "Purple", + "prevo": "spoink", + "evoLevel": 32, + "eggGroups": [ + "Field" + ] + }, + "spinda": { + "num": 327, + "species": "Spinda", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 60, + "atk": 60, + "def": 60, + "spa": 60, + "spd": 60, + "spe": 60 + }, + "abilities": { + "0": "Own Tempo", + "1": "Tangled Feet", + "H": "Contrary" + }, + "heightm": 1.1, + "weightkg": 5, + "color": "Brown", + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "trapinch": { + "num": 328, + "species": "Trapinch", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 45, + "atk": 100, + "def": 45, + "spa": 45, + "spd": 45, + "spe": 10 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Arena Trap", + "H": "Sheer Force" + }, + "heightm": 0.7, + "weightkg": 15, + "color": "Brown", + "evos": [ + "vibrava" + ], + "eggGroups": [ + "Bug" + ] + }, + "vibrava": { + "num": 329, + "species": "Vibrava", + "types": [ + "Ground", + "Dragon" + ], + "baseStats": { + "hp": 50, + "atk": 70, + "def": 50, + "spa": 50, + "spd": 50, + "spe": 70 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.1, + "weightkg": 15.3, + "color": "Green", + "prevo": "trapinch", + "evos": [ + "flygon" + ], + "evoLevel": 35, + "eggGroups": [ + "Bug" + ] + }, + "flygon": { + "num": 330, + "species": "Flygon", + "types": [ + "Ground", + "Dragon" + ], + "baseStats": { + "hp": 80, + "atk": 100, + "def": 80, + "spa": 80, + "spd": 80, + "spe": 100 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 2, + "weightkg": 82, + "color": "Green", + "prevo": "vibrava", + "evoLevel": 45, + "eggGroups": [ + "Bug" + ] + }, + "cacnea": { + "num": 331, + "species": "Cacnea", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 50, + "atk": 85, + "def": 40, + "spa": 85, + "spd": 40, + "spe": 35 + }, + "abilities": { + "0": "Sand Veil", + "H": "Water Absorb" + }, + "heightm": 0.4, + "weightkg": 51.3, + "color": "Green", + "evos": [ + "cacturne" + ], + "eggGroups": [ + "Grass", + "Human-Like" + ] + }, + "cacturne": { + "num": 332, + "species": "Cacturne", + "types": [ + "Grass", + "Dark" + ], + "baseStats": { + "hp": 70, + "atk": 115, + "def": 60, + "spa": 115, + "spd": 60, + "spe": 55 + }, + "abilities": { + "0": "Sand Veil", + "H": "Water Absorb" + }, + "heightm": 1.3, + "weightkg": 77.4, + "color": "Green", + "prevo": "cacnea", + "evoLevel": 32, + "eggGroups": [ + "Grass", + "Human-Like" + ] + }, + "swablu": { + "num": 333, + "species": "Swablu", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 45, + "atk": 40, + "def": 60, + "spa": 40, + "spd": 75, + "spe": 50 + }, + "abilities": { + "0": "Natural Cure", + "H": "Cloud Nine" + }, + "heightm": 0.4, + "weightkg": 1.2, + "color": "Blue", + "evos": [ + "altaria" + ], + "eggGroups": [ + "Flying", + "Dragon" + ] + }, + "altaria": { + "num": 334, + "species": "Altaria", + "types": [ + "Dragon", + "Flying" + ], + "baseStats": { + "hp": 75, + "atk": 70, + "def": 90, + "spa": 70, + "spd": 105, + "spe": 80 + }, + "abilities": { + "0": "Natural Cure", + "H": "Cloud Nine" + }, + "heightm": 1.1, + "weightkg": 20.6, + "color": "Blue", + "prevo": "swablu", + "evoLevel": 35, + "eggGroups": [ + "Flying", + "Dragon" + ], + "otherFormes": [ + "altariamega" + ] + }, + "altariamega": { + "num": 334, + "species": "Altaria-Mega", + "baseSpecies": "Altaria", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dragon", + "Fairy" + ], + "baseStats": { + "hp": 75, + "atk": 110, + "def": 110, + "spa": 110, + "spd": 105, + "spe": 80 + }, + "abilities": { + "0": "Pixilate" + }, + "heightm": 1.5, + "weightkg": 20.6, + "color": "Blue", + "eggGroups": [ + "Flying", + "Dragon" + ] + }, + "zangoose": { + "num": 335, + "species": "Zangoose", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 73, + "atk": 115, + "def": 60, + "spa": 60, + "spd": 60, + "spe": 90 + }, + "abilities": { + "0": "Immunity", + "H": "Toxic Boost" + }, + "heightm": 1.3, + "weightkg": 40.3, + "color": "White", + "eggGroups": [ + "Field" + ] + }, + "seviper": { + "num": 336, + "species": "Seviper", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 73, + "atk": 100, + "def": 60, + "spa": 100, + "spd": 60, + "spe": 65 + }, + "abilities": { + "0": "Shed Skin", + "H": "Infiltrator" + }, + "heightm": 2.7, + "weightkg": 52.5, + "color": "Black", + "eggGroups": [ + "Field", + "Dragon" + ] + }, + "lunatone": { + "num": 337, + "species": "Lunatone", + "types": [ + "Rock", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 70, + "atk": 55, + "def": 65, + "spa": 95, + "spd": 85, + "spe": 70 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1, + "weightkg": 168, + "color": "Yellow", + "eggGroups": [ + "Mineral" + ] + }, + "solrock": { + "num": 338, + "species": "Solrock", + "types": [ + "Rock", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 70, + "atk": 95, + "def": 85, + "spa": 55, + "spd": 65, + "spe": 70 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.2, + "weightkg": 154, + "color": "Red", + "eggGroups": [ + "Mineral" + ] + }, + "barboach": { + "num": 339, + "species": "Barboach", + "types": [ + "Water", + "Ground" + ], + "baseStats": { + "hp": 50, + "atk": 48, + "def": 43, + "spa": 46, + "spd": 41, + "spe": 60 + }, + "abilities": { + "0": "Oblivious", + "1": "Anticipation", + "H": "Hydration" + }, + "heightm": 0.4, + "weightkg": 1.9, + "color": "Gray", + "evos": [ + "whiscash" + ], + "eggGroups": [ + "Water 2" + ] + }, + "whiscash": { + "num": 340, + "species": "Whiscash", + "types": [ + "Water", + "Ground" + ], + "baseStats": { + "hp": 110, + "atk": 78, + "def": 73, + "spa": 76, + "spd": 71, + "spe": 60 + }, + "abilities": { + "0": "Oblivious", + "1": "Anticipation", + "H": "Hydration" + }, + "heightm": 0.9, + "weightkg": 23.6, + "color": "Blue", + "prevo": "barboach", + "evoLevel": 30, + "eggGroups": [ + "Water 2" + ] + }, + "corphish": { + "num": 341, + "species": "Corphish", + "types": [ + "Water" + ], + "baseStats": { + "hp": 43, + "atk": 80, + "def": 65, + "spa": 50, + "spd": 35, + "spe": 35 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Shell Armor", + "H": "Adaptability" + }, + "heightm": 0.6, + "weightkg": 11.5, + "color": "Red", + "evos": [ + "crawdaunt" + ], + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "crawdaunt": { + "num": 342, + "species": "Crawdaunt", + "types": [ + "Water", + "Dark" + ], + "baseStats": { + "hp": 63, + "atk": 120, + "def": 85, + "spa": 90, + "spd": 55, + "spe": 55 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Shell Armor", + "H": "Adaptability" + }, + "heightm": 1.1, + "weightkg": 32.8, + "color": "Red", + "prevo": "corphish", + "evoLevel": 30, + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "baltoy": { + "num": 343, + "species": "Baltoy", + "types": [ + "Ground", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 40, + "atk": 40, + "def": 55, + "spa": 40, + "spd": 70, + "spe": 55 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.5, + "weightkg": 21.5, + "color": "Brown", + "evos": [ + "claydol" + ], + "eggGroups": [ + "Mineral" + ] + }, + "claydol": { + "num": 344, + "species": "Claydol", + "types": [ + "Ground", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 60, + "atk": 70, + "def": 105, + "spa": 70, + "spd": 120, + "spe": 75 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.5, + "weightkg": 108, + "color": "Black", + "prevo": "baltoy", + "evoLevel": 36, + "eggGroups": [ + "Mineral" + ] + }, + "lileep": { + "num": 345, + "species": "Lileep", + "types": [ + "Rock", + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 66, + "atk": 41, + "def": 77, + "spa": 61, + "spd": 87, + "spe": 23 + }, + "abilities": { + "0": "Suction Cups", + "H": "Storm Drain" + }, + "heightm": 1, + "weightkg": 23.8, + "color": "Purple", + "evos": [ + "cradily" + ], + "eggGroups": [ + "Water 3" + ] + }, + "cradily": { + "num": 346, + "species": "Cradily", + "types": [ + "Rock", + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 86, + "atk": 81, + "def": 97, + "spa": 81, + "spd": 107, + "spe": 43 + }, + "abilities": { + "0": "Suction Cups", + "H": "Storm Drain" + }, + "heightm": 1.5, + "weightkg": 60.4, + "color": "Green", + "prevo": "lileep", + "evoLevel": 40, + "eggGroups": [ + "Water 3" + ] + }, + "anorith": { + "num": 347, + "species": "Anorith", + "types": [ + "Rock", + "Bug" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 45, + "atk": 95, + "def": 50, + "spa": 40, + "spd": 50, + "spe": 75 + }, + "abilities": { + "0": "Battle Armor", + "H": "Swift Swim" + }, + "heightm": 0.7, + "weightkg": 12.5, + "color": "Gray", + "evos": [ + "armaldo" + ], + "eggGroups": [ + "Water 3" + ] + }, + "armaldo": { + "num": 348, + "species": "Armaldo", + "types": [ + "Rock", + "Bug" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 125, + "def": 100, + "spa": 70, + "spd": 80, + "spe": 45 + }, + "abilities": { + "0": "Battle Armor", + "H": "Swift Swim" + }, + "heightm": 1.5, + "weightkg": 68.2, + "color": "Gray", + "prevo": "anorith", + "evoLevel": 40, + "eggGroups": [ + "Water 3" + ] + }, + "feebas": { + "num": 349, + "species": "Feebas", + "types": [ + "Water" + ], + "baseStats": { + "hp": 20, + "atk": 15, + "def": 20, + "spa": 10, + "spd": 55, + "spe": 80 + }, + "abilities": { + "0": "Swift Swim", + "1": "Oblivious", + "H": "Adaptability" + }, + "heightm": 0.6, + "weightkg": 7.4, + "color": "Brown", + "evos": [ + "milotic" + ], + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "milotic": { + "num": 350, + "species": "Milotic", + "types": [ + "Water" + ], + "baseStats": { + "hp": 95, + "atk": 60, + "def": 79, + "spa": 100, + "spd": 125, + "spe": 81 + }, + "abilities": { + "0": "Marvel Scale", + "1": "Competitive", + "H": "Cute Charm" + }, + "heightm": 6.2, + "weightkg": 162, + "color": "Pink", + "prevo": "feebas", + "evoLevel": 1, + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "castform": { + "num": 351, + "species": "Castform", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 70, + "atk": 70, + "def": 70, + "spa": 70, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Forecast" + }, + "heightm": 0.3, + "weightkg": 0.8, + "color": "White", + "eggGroups": [ + "Fairy", + "Amorphous" + ], + "otherFormes": [ + "castformsunny", + "castformrainy", + "castformsnowy" + ] + }, + "castformsunny": { + "num": 351, + "species": "Castform-Sunny", + "baseSpecies": "Castform", + "forme": "Sunny", + "formeLetter": "S", + "types": [ + "Fire" + ], + "baseStats": { + "hp": 70, + "atk": 70, + "def": 70, + "spa": 70, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Forecast" + }, + "heightm": 0.3, + "weightkg": 0.8, + "color": "White", + "eggGroups": [ + "Fairy", + "Amorphous" + ] + }, + "castformrainy": { + "num": 351, + "species": "Castform-Rainy", + "baseSpecies": "Castform", + "forme": "Rainy", + "formeLetter": "R", + "types": [ + "Water" + ], + "baseStats": { + "hp": 70, + "atk": 70, + "def": 70, + "spa": 70, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Forecast" + }, + "heightm": 0.3, + "weightkg": 0.8, + "color": "White", + "eggGroups": [ + "Fairy", + "Amorphous" + ] + }, + "castformsnowy": { + "num": 351, + "species": "Castform-Snowy", + "baseSpecies": "Castform", + "forme": "Snowy", + "formeLetter": "S", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 70, + "atk": 70, + "def": 70, + "spa": 70, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Forecast" + }, + "heightm": 0.3, + "weightkg": 0.8, + "color": "White", + "eggGroups": [ + "Fairy", + "Amorphous" + ] + }, + "kecleon": { + "num": 352, + "species": "Kecleon", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 60, + "atk": 90, + "def": 70, + "spa": 60, + "spd": 120, + "spe": 40 + }, + "abilities": { + "0": "Color Change", + "H": "Protean" + }, + "heightm": 1, + "weightkg": 22, + "color": "Green", + "eggGroups": [ + "Field" + ] + }, + "shuppet": { + "num": 353, + "species": "Shuppet", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 44, + "atk": 75, + "def": 35, + "spa": 63, + "spd": 33, + "spe": 45 + }, + "abilities": { + "0": "Insomnia", + "1": "Frisk", + "H": "Cursed Body" + }, + "heightm": 0.6, + "weightkg": 2.3, + "color": "Black", + "evos": [ + "banette" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "banette": { + "num": 354, + "species": "Banette", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 64, + "atk": 115, + "def": 65, + "spa": 83, + "spd": 63, + "spe": 65 + }, + "abilities": { + "0": "Insomnia", + "1": "Frisk", + "H": "Cursed Body" + }, + "heightm": 1.1, + "weightkg": 12.5, + "color": "Black", + "prevo": "shuppet", + "evoLevel": 37, + "eggGroups": [ + "Amorphous" + ], + "otherFormes": [ + "banettemega" + ] + }, + "banettemega": { + "num": 354, + "species": "Banette-Mega", + "baseSpecies": "Banette", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 64, + "atk": 165, + "def": 75, + "spa": 93, + "spd": 83, + "spe": 75 + }, + "abilities": { + "0": "Prankster" + }, + "heightm": 1.2, + "weightkg": 13, + "color": "Black", + "eggGroups": [ + "Amorphous" + ] + }, + "duskull": { + "num": 355, + "species": "Duskull", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 20, + "atk": 40, + "def": 90, + "spa": 30, + "spd": 90, + "spe": 25 + }, + "abilities": { + "0": "Levitate", + "H": "Frisk" + }, + "heightm": 0.8, + "weightkg": 15, + "color": "Black", + "evos": [ + "dusclops" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "dusclops": { + "num": 356, + "species": "Dusclops", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 40, + "atk": 70, + "def": 130, + "spa": 60, + "spd": 130, + "spe": 25 + }, + "abilities": { + "0": "Pressure", + "H": "Frisk" + }, + "heightm": 1.6, + "weightkg": 30.6, + "color": "Black", + "prevo": "duskull", + "evos": [ + "dusknoir" + ], + "evoLevel": 37, + "eggGroups": [ + "Amorphous" + ] + }, + "tropius": { + "num": 357, + "species": "Tropius", + "types": [ + "Grass", + "Flying" + ], + "baseStats": { + "hp": 99, + "atk": 68, + "def": 83, + "spa": 72, + "spd": 87, + "spe": 51 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Solar Power", + "H": "Harvest" + }, + "heightm": 2, + "weightkg": 100, + "color": "Green", + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "chimecho": { + "num": 358, + "species": "Chimecho", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 65, + "atk": 50, + "def": 70, + "spa": 95, + "spd": 80, + "spe": 65 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.6, + "weightkg": 1, + "color": "Blue", + "prevo": "chingling", + "evoLevel": 1, + "eggGroups": [ + "Amorphous" + ] + }, + "absol": { + "num": 359, + "species": "Absol", + "types": [ + "Dark" + ], + "baseStats": { + "hp": 65, + "atk": 130, + "def": 60, + "spa": 75, + "spd": 60, + "spe": 75 + }, + "abilities": { + "0": "Pressure", + "1": "Super Luck", + "H": "Justified" + }, + "heightm": 1.2, + "weightkg": 47, + "color": "White", + "eggGroups": [ + "Field" + ], + "otherFormes": [ + "absolmega" + ] + }, + "absolmega": { + "num": 359, + "species": "Absol-Mega", + "baseSpecies": "Absol", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dark" + ], + "baseStats": { + "hp": 65, + "atk": 150, + "def": 60, + "spa": 115, + "spd": 60, + "spe": 115 + }, + "abilities": { + "0": "Magic Bounce" + }, + "heightm": 1.2, + "weightkg": 49, + "color": "White", + "eggGroups": [ + "Field" + ] + }, + "wynaut": { + "num": 360, + "species": "Wynaut", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 95, + "atk": 23, + "def": 48, + "spa": 23, + "spd": 48, + "spe": 23 + }, + "abilities": { + "0": "Shadow Tag", + "H": "Telepathy" + }, + "heightm": 0.6, + "weightkg": 14, + "color": "Blue", + "evos": [ + "wobbuffet" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "snorunt": { + "num": 361, + "species": "Snorunt", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 50, + "atk": 50, + "def": 50, + "spa": 50, + "spd": 50, + "spe": 50 + }, + "abilities": { + "0": "Inner Focus", + "1": "Ice Body", + "H": "Moody" + }, + "heightm": 0.7, + "weightkg": 16.8, + "color": "Gray", + "evos": [ + "glalie", + "froslass" + ], + "eggGroups": [ + "Fairy", + "Mineral" + ] + }, + "glalie": { + "num": 362, + "species": "Glalie", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 80, + "atk": 80, + "def": 80, + "spa": 80, + "spd": 80, + "spe": 80 + }, + "abilities": { + "0": "Inner Focus", + "1": "Ice Body", + "H": "Moody" + }, + "heightm": 1.5, + "weightkg": 256.5, + "color": "Gray", + "prevo": "snorunt", + "evoLevel": 42, + "eggGroups": [ + "Fairy", + "Mineral" + ], + "otherFormes": [ + "glaliemega" + ] + }, + "glaliemega": { + "num": 362, + "species": "Glalie-Mega", + "baseSpecies": "Glalie", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 80, + "atk": 120, + "def": 80, + "spa": 120, + "spd": 80, + "spe": 100 + }, + "abilities": { + "0": "Refrigerate" + }, + "heightm": 2.1, + "weightkg": 350.2, + "color": "Gray", + "eggGroups": [ + "Fairy", + "Mineral" + ] + }, + "spheal": { + "num": 363, + "species": "Spheal", + "types": [ + "Ice", + "Water" + ], + "baseStats": { + "hp": 70, + "atk": 40, + "def": 50, + "spa": 55, + "spd": 50, + "spe": 25 + }, + "abilities": { + "0": "Thick Fat", + "1": "Ice Body", + "H": "Oblivious" + }, + "heightm": 0.8, + "weightkg": 39.5, + "color": "Blue", + "evos": [ + "sealeo" + ], + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "sealeo": { + "num": 364, + "species": "Sealeo", + "types": [ + "Ice", + "Water" + ], + "baseStats": { + "hp": 90, + "atk": 60, + "def": 70, + "spa": 75, + "spd": 70, + "spe": 45 + }, + "abilities": { + "0": "Thick Fat", + "1": "Ice Body", + "H": "Oblivious" + }, + "heightm": 1.1, + "weightkg": 87.6, + "color": "Blue", + "prevo": "spheal", + "evos": [ + "walrein" + ], + "evoLevel": 32, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "walrein": { + "num": 365, + "species": "Walrein", + "types": [ + "Ice", + "Water" + ], + "baseStats": { + "hp": 110, + "atk": 80, + "def": 90, + "spa": 95, + "spd": 90, + "spe": 65 + }, + "abilities": { + "0": "Thick Fat", + "1": "Ice Body", + "H": "Oblivious" + }, + "heightm": 1.4, + "weightkg": 150.6, + "color": "Blue", + "prevo": "sealeo", + "evoLevel": 44, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "clamperl": { + "num": 366, + "species": "Clamperl", + "types": [ + "Water" + ], + "baseStats": { + "hp": 35, + "atk": 64, + "def": 85, + "spa": 74, + "spd": 55, + "spe": 32 + }, + "abilities": { + "0": "Shell Armor", + "H": "Rattled" + }, + "heightm": 0.4, + "weightkg": 52.5, + "color": "Blue", + "evos": [ + "huntail", + "gorebyss" + ], + "eggGroups": [ + "Water 1" + ] + }, + "huntail": { + "num": 367, + "species": "Huntail", + "types": [ + "Water" + ], + "baseStats": { + "hp": 55, + "atk": 104, + "def": 105, + "spa": 94, + "spd": 75, + "spe": 52 + }, + "abilities": { + "0": "Swift Swim", + "H": "Water Veil" + }, + "heightm": 1.7, + "weightkg": 27, + "color": "Blue", + "prevo": "clamperl", + "evoLevel": 1, + "eggGroups": [ + "Water 1" + ] + }, + "gorebyss": { + "num": 368, + "species": "Gorebyss", + "types": [ + "Water" + ], + "baseStats": { + "hp": 55, + "atk": 84, + "def": 105, + "spa": 114, + "spd": 75, + "spe": 52 + }, + "abilities": { + "0": "Swift Swim", + "H": "Hydration" + }, + "heightm": 1.8, + "weightkg": 22.6, + "color": "Pink", + "prevo": "clamperl", + "evoLevel": 1, + "eggGroups": [ + "Water 1" + ] + }, + "relicanth": { + "num": 369, + "species": "Relicanth", + "types": [ + "Water", + "Rock" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 100, + "atk": 90, + "def": 130, + "spa": 45, + "spd": 65, + "spe": 55 + }, + "abilities": { + "0": "Swift Swim", + "1": "Rock Head", + "H": "Sturdy" + }, + "heightm": 1, + "weightkg": 23.4, + "color": "Gray", + "eggGroups": [ + "Water 1", + "Water 2" + ] + }, + "luvdisc": { + "num": 370, + "species": "Luvdisc", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 43, + "atk": 30, + "def": 55, + "spa": 40, + "spd": 65, + "spe": 97 + }, + "abilities": { + "0": "Swift Swim", + "H": "Hydration" + }, + "heightm": 0.6, + "weightkg": 8.7, + "color": "Pink", + "eggGroups": [ + "Water 2" + ] + }, + "bagon": { + "num": 371, + "species": "Bagon", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 45, + "atk": 75, + "def": 60, + "spa": 40, + "spd": 30, + "spe": 50 + }, + "abilities": { + "0": "Rock Head", + "H": "Sheer Force" + }, + "heightm": 0.6, + "weightkg": 42.1, + "color": "Blue", + "evos": [ + "shelgon" + ], + "eggGroups": [ + "Dragon" + ] + }, + "shelgon": { + "num": 372, + "species": "Shelgon", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 65, + "atk": 95, + "def": 100, + "spa": 60, + "spd": 50, + "spe": 50 + }, + "abilities": { + "0": "Rock Head", + "H": "Overcoat" + }, + "heightm": 1.1, + "weightkg": 110.5, + "color": "White", + "prevo": "bagon", + "evos": [ + "salamence" + ], + "evoLevel": 30, + "eggGroups": [ + "Dragon" + ] + }, + "salamence": { + "num": 373, + "species": "Salamence", + "types": [ + "Dragon", + "Flying" + ], + "baseStats": { + "hp": 95, + "atk": 135, + "def": 80, + "spa": 110, + "spd": 80, + "spe": 100 + }, + "abilities": { + "0": "Intimidate", + "H": "Moxie" + }, + "heightm": 1.5, + "weightkg": 102.6, + "color": "Blue", + "prevo": "shelgon", + "evoLevel": 50, + "eggGroups": [ + "Dragon" + ], + "otherFormes": [ + "salamencemega" + ] + }, + "salamencemega": { + "num": 373, + "species": "Salamence-Mega", + "baseSpecies": "Salamence", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dragon", + "Flying" + ], + "baseStats": { + "hp": 95, + "atk": 145, + "def": 130, + "spa": 120, + "spd": 90, + "spe": 120 + }, + "abilities": { + "0": "Aerilate" + }, + "heightm": 1.8, + "weightkg": 112.6, + "color": "Blue", + "eggGroups": [ + "Dragon" + ] + }, + "beldum": { + "num": 374, + "species": "Beldum", + "types": [ + "Steel", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 40, + "atk": 55, + "def": 80, + "spa": 35, + "spd": 60, + "spe": 30 + }, + "abilities": { + "0": "Clear Body", + "H": "Light Metal" + }, + "heightm": 0.6, + "weightkg": 95.2, + "color": "Blue", + "evos": [ + "metang" + ], + "eggGroups": [ + "Mineral" + ] + }, + "metang": { + "num": 375, + "species": "Metang", + "types": [ + "Steel", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 60, + "atk": 75, + "def": 100, + "spa": 55, + "spd": 80, + "spe": 50 + }, + "abilities": { + "0": "Clear Body", + "H": "Light Metal" + }, + "heightm": 1.2, + "weightkg": 202.5, + "color": "Blue", + "prevo": "beldum", + "evos": [ + "metagross" + ], + "evoLevel": 20, + "eggGroups": [ + "Mineral" + ] + }, + "metagross": { + "num": 376, + "species": "Metagross", + "types": [ + "Steel", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 135, + "def": 130, + "spa": 95, + "spd": 90, + "spe": 70 + }, + "abilities": { + "0": "Clear Body", + "H": "Light Metal" + }, + "heightm": 1.6, + "weightkg": 550, + "color": "Blue", + "prevo": "metang", + "evoLevel": 45, + "eggGroups": [ + "Mineral" + ], + "otherFormes": [ + "metagrossmega" + ] + }, + "metagrossmega": { + "num": 376, + "species": "Metagross-Mega", + "baseSpecies": "Metagross", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Steel", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 145, + "def": 150, + "spa": 105, + "spd": 110, + "spe": 110 + }, + "abilities": { + "0": "Tough Claws" + }, + "heightm": 2.5, + "weightkg": 942.9, + "color": "Blue", + "eggGroups": [ + "Mineral" + ] + }, + "regirock": { + "num": 377, + "species": "Regirock", + "types": [ + "Rock" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 100, + "def": 200, + "spa": 50, + "spd": 100, + "spe": 50 + }, + "abilities": { + "0": "Clear Body", + "H": "Sturdy" + }, + "heightm": 1.7, + "weightkg": 230, + "color": "Brown", + "eggGroups": [ + "Undiscovered" + ] + }, + "regice": { + "num": 378, + "species": "Regice", + "types": [ + "Ice" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 50, + "def": 100, + "spa": 100, + "spd": 200, + "spe": 50 + }, + "abilities": { + "0": "Clear Body", + "H": "Ice Body" + }, + "heightm": 1.8, + "weightkg": 175, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "registeel": { + "num": 379, + "species": "Registeel", + "types": [ + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 75, + "def": 150, + "spa": 75, + "spd": 150, + "spe": 50 + }, + "abilities": { + "0": "Clear Body", + "H": "Light Metal" + }, + "heightm": 1.9, + "weightkg": 205, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "latias": { + "num": 380, + "species": "Latias", + "types": [ + "Dragon", + "Psychic" + ], + "gender": "F", + "baseStats": { + "hp": 80, + "atk": 80, + "def": 90, + "spa": 110, + "spd": 130, + "spe": 110 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.4, + "weightkg": 40, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "latiasmega" + ] + }, + "latiasmega": { + "num": 380, + "species": "Latias-Mega", + "baseSpecies": "Latias", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dragon", + "Psychic" + ], + "gender": "F", + "baseStats": { + "hp": 80, + "atk": 100, + "def": 120, + "spa": 140, + "spd": 150, + "spe": 110 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.8, + "weightkg": 52, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ] + }, + "latios": { + "num": 381, + "species": "Latios", + "types": [ + "Dragon", + "Psychic" + ], + "gender": "M", + "baseStats": { + "hp": 80, + "atk": 90, + "def": 80, + "spa": 130, + "spd": 110, + "spe": 110 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 2, + "weightkg": 60, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "latiosmega" + ] + }, + "latiosmega": { + "num": 381, + "species": "Latios-Mega", + "baseSpecies": "Latios", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dragon", + "Psychic" + ], + "gender": "M", + "baseStats": { + "hp": 80, + "atk": 130, + "def": 100, + "spa": 160, + "spd": 120, + "spe": 110 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 2.3, + "weightkg": 70, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "kyogre": { + "num": 382, + "species": "Kyogre", + "types": [ + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 100, + "def": 90, + "spa": 150, + "spd": 140, + "spe": 90 + }, + "abilities": { + "0": "Drizzle" + }, + "heightm": 4.5, + "weightkg": 352, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "kyogreprimal" + ] + }, + "kyogreprimal": { + "num": 382, + "species": "Kyogre-Primal", + "baseSpecies": "Kyogre", + "forme": "Primal", + "formeLetter": "P", + "types": [ + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 150, + "def": 90, + "spa": 180, + "spd": 160, + "spe": 90 + }, + "abilities": { + "0": "Primordial Sea" + }, + "heightm": 9.8, + "weightkg": 430, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "groudon": { + "num": 383, + "species": "Groudon", + "types": [ + "Ground" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 150, + "def": 140, + "spa": 100, + "spd": 90, + "spe": 90 + }, + "abilities": { + "0": "Drought" + }, + "heightm": 3.5, + "weightkg": 950, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "groudonprimal" + ] + }, + "groudonprimal": { + "num": 383, + "species": "Groudon-Primal", + "baseSpecies": "Groudon", + "forme": "Primal", + "formeLetter": "P", + "types": [ + "Ground", + "Fire" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 180, + "def": 160, + "spa": 150, + "spd": 90, + "spe": 90 + }, + "abilities": { + "0": "Desolate Land" + }, + "heightm": 5, + "weightkg": 999.7, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ] + }, + "rayquaza": { + "num": 384, + "species": "Rayquaza", + "types": [ + "Dragon", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 105, + "atk": 150, + "def": 90, + "spa": 150, + "spd": 90, + "spe": 95 + }, + "abilities": { + "0": "Air Lock" + }, + "heightm": 7, + "weightkg": 206.5, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "rayquazamega" + ] + }, + "rayquazamega": { + "num": 384, + "species": "Rayquaza-Mega", + "baseSpecies": "Rayquaza", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dragon", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 105, + "atk": 180, + "def": 100, + "spa": 180, + "spd": 100, + "spe": 115 + }, + "abilities": { + "0": "Delta Stream" + }, + "heightm": 10.8, + "weightkg": 392, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ] + }, + "jirachi": { + "num": 385, + "species": "Jirachi", + "types": [ + "Steel", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 100, + "def": 100, + "spa": 100, + "spd": 100, + "spe": 100 + }, + "abilities": { + "0": "Serene Grace" + }, + "heightm": 0.3, + "weightkg": 1.1, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "deoxys": { + "num": 386, + "species": "Deoxys", + "baseForme": "Normal", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 150, + "def": 50, + "spa": 150, + "spd": 50, + "spe": 150 + }, + "abilities": { + "0": "Pressure" + }, + "heightm": 1.7, + "weightkg": 60.8, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "deoxysattack", + "deoxysdefense", + "deoxysspeed" + ] + }, + "deoxysattack": { + "num": 386, + "species": "Deoxys-Attack", + "baseSpecies": "Deoxys", + "forme": "Attack", + "formeLetter": "A", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 180, + "def": 20, + "spa": 180, + "spd": 20, + "spe": 150 + }, + "abilities": { + "0": "Pressure" + }, + "heightm": 1.7, + "weightkg": 60.8, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ] + }, + "deoxysdefense": { + "num": 386, + "species": "Deoxys-Defense", + "baseSpecies": "Deoxys", + "forme": "Defense", + "formeLetter": "D", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 70, + "def": 160, + "spa": 70, + "spd": 160, + "spe": 90 + }, + "abilities": { + "0": "Pressure" + }, + "heightm": 1.7, + "weightkg": 60.8, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ] + }, + "deoxysspeed": { + "num": 386, + "species": "Deoxys-Speed", + "baseSpecies": "Deoxys", + "forme": "Speed", + "formeLetter": "S", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 95, + "def": 90, + "spa": 95, + "spd": 90, + "spe": 180 + }, + "abilities": { + "0": "Pressure" + }, + "heightm": 1.7, + "weightkg": 60.8, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ] + }, + "turtwig": { + "num": 387, + "species": "Turtwig", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 55, + "atk": 68, + "def": 64, + "spa": 45, + "spd": 55, + "spe": 31 + }, + "abilities": { + "0": "Overgrow", + "H": "Shell Armor" + }, + "heightm": 0.4, + "weightkg": 10.2, + "color": "Green", + "evos": [ + "grotle" + ], + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "grotle": { + "num": 388, + "species": "Grotle", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 89, + "def": 85, + "spa": 55, + "spd": 65, + "spe": 36 + }, + "abilities": { + "0": "Overgrow", + "H": "Shell Armor" + }, + "heightm": 1.1, + "weightkg": 97, + "color": "Green", + "prevo": "turtwig", + "evos": [ + "torterra" + ], + "evoLevel": 18, + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "torterra": { + "num": 389, + "species": "Torterra", + "types": [ + "Grass", + "Ground" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 95, + "atk": 109, + "def": 105, + "spa": 75, + "spd": 85, + "spe": 56 + }, + "abilities": { + "0": "Overgrow", + "H": "Shell Armor" + }, + "heightm": 2.2, + "weightkg": 310, + "color": "Green", + "prevo": "grotle", + "evoLevel": 32, + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "chimchar": { + "num": 390, + "species": "Chimchar", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 44, + "atk": 58, + "def": 44, + "spa": 58, + "spd": 44, + "spe": 61 + }, + "abilities": { + "0": "Blaze", + "H": "Iron Fist" + }, + "heightm": 0.5, + "weightkg": 6.2, + "color": "Brown", + "evos": [ + "monferno" + ], + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "monferno": { + "num": 391, + "species": "Monferno", + "types": [ + "Fire", + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 64, + "atk": 78, + "def": 52, + "spa": 78, + "spd": 52, + "spe": 81 + }, + "abilities": { + "0": "Blaze", + "H": "Iron Fist" + }, + "heightm": 0.9, + "weightkg": 22, + "color": "Brown", + "prevo": "chimchar", + "evos": [ + "infernape" + ], + "evoLevel": 14, + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "infernape": { + "num": 392, + "species": "Infernape", + "types": [ + "Fire", + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 76, + "atk": 104, + "def": 71, + "spa": 104, + "spd": 71, + "spe": 108 + }, + "abilities": { + "0": "Blaze", + "H": "Iron Fist" + }, + "heightm": 1.2, + "weightkg": 55, + "color": "Brown", + "prevo": "monferno", + "evoLevel": 36, + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "piplup": { + "num": 393, + "species": "Piplup", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 53, + "atk": 51, + "def": 53, + "spa": 61, + "spd": 56, + "spe": 40 + }, + "abilities": { + "0": "Torrent", + "H": "Defiant" + }, + "heightm": 0.4, + "weightkg": 5.2, + "color": "Blue", + "evos": [ + "prinplup" + ], + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "prinplup": { + "num": 394, + "species": "Prinplup", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 64, + "atk": 66, + "def": 68, + "spa": 81, + "spd": 76, + "spe": 50 + }, + "abilities": { + "0": "Torrent", + "H": "Defiant" + }, + "heightm": 0.8, + "weightkg": 23, + "color": "Blue", + "prevo": "piplup", + "evos": [ + "empoleon" + ], + "evoLevel": 16, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "empoleon": { + "num": 395, + "species": "Empoleon", + "types": [ + "Water", + "Steel" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 84, + "atk": 86, + "def": 88, + "spa": 111, + "spd": 101, + "spe": 60 + }, + "abilities": { + "0": "Torrent", + "H": "Defiant" + }, + "heightm": 1.7, + "weightkg": 84.5, + "color": "Blue", + "prevo": "prinplup", + "evoLevel": 36, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "starly": { + "num": 396, + "species": "Starly", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 40, + "atk": 55, + "def": 30, + "spa": 30, + "spd": 30, + "spe": 60 + }, + "abilities": { + "0": "Keen Eye", + "H": "Reckless" + }, + "heightm": 0.3, + "weightkg": 2, + "color": "Brown", + "evos": [ + "staravia" + ], + "eggGroups": [ + "Flying" + ] + }, + "staravia": { + "num": 397, + "species": "Staravia", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 55, + "atk": 75, + "def": 50, + "spa": 40, + "spd": 40, + "spe": 80 + }, + "abilities": { + "0": "Intimidate", + "H": "Reckless" + }, + "heightm": 0.6, + "weightkg": 15.5, + "color": "Brown", + "prevo": "starly", + "evos": [ + "staraptor" + ], + "evoLevel": 14, + "eggGroups": [ + "Flying" + ] + }, + "staraptor": { + "num": 398, + "species": "Staraptor", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 85, + "atk": 120, + "def": 70, + "spa": 50, + "spd": 60, + "spe": 100 + }, + "abilities": { + "0": "Intimidate", + "H": "Reckless" + }, + "heightm": 1.2, + "weightkg": 24.9, + "color": "Brown", + "prevo": "staravia", + "evoLevel": 34, + "eggGroups": [ + "Flying" + ] + }, + "bidoof": { + "num": 399, + "species": "Bidoof", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 59, + "atk": 45, + "def": 40, + "spa": 35, + "spd": 40, + "spe": 31 + }, + "abilities": { + "0": "Simple", + "1": "Unaware", + "H": "Moody" + }, + "heightm": 0.5, + "weightkg": 20, + "color": "Brown", + "evos": [ + "bibarel" + ], + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "bibarel": { + "num": 400, + "species": "Bibarel", + "types": [ + "Normal", + "Water" + ], + "baseStats": { + "hp": 79, + "atk": 85, + "def": 60, + "spa": 55, + "spd": 60, + "spe": 71 + }, + "abilities": { + "0": "Simple", + "1": "Unaware", + "H": "Moody" + }, + "heightm": 1, + "weightkg": 31.5, + "color": "Brown", + "prevo": "bidoof", + "evoLevel": 15, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "kricketot": { + "num": 401, + "species": "Kricketot", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 37, + "atk": 25, + "def": 41, + "spa": 25, + "spd": 41, + "spe": 25 + }, + "abilities": { + "0": "Shed Skin", + "H": "Run Away" + }, + "heightm": 0.3, + "weightkg": 2.2, + "color": "Red", + "evos": [ + "kricketune" + ], + "eggGroups": [ + "Bug" + ] + }, + "kricketune": { + "num": 402, + "species": "Kricketune", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 77, + "atk": 85, + "def": 51, + "spa": 55, + "spd": 51, + "spe": 65 + }, + "abilities": { + "0": "Swarm", + "H": "Technician" + }, + "heightm": 1, + "weightkg": 25.5, + "color": "Red", + "prevo": "kricketot", + "evoLevel": 10, + "eggGroups": [ + "Bug" + ] + }, + "shinx": { + "num": 403, + "species": "Shinx", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 45, + "atk": 65, + "def": 34, + "spa": 40, + "spd": 34, + "spe": 45 + }, + "abilities": { + "0": "Rivalry", + "1": "Intimidate", + "H": "Guts" + }, + "heightm": 0.5, + "weightkg": 9.5, + "color": "Blue", + "evos": [ + "luxio" + ], + "eggGroups": [ + "Field" + ] + }, + "luxio": { + "num": 404, + "species": "Luxio", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 60, + "atk": 85, + "def": 49, + "spa": 60, + "spd": 49, + "spe": 60 + }, + "abilities": { + "0": "Rivalry", + "1": "Intimidate", + "H": "Guts" + }, + "heightm": 0.9, + "weightkg": 30.5, + "color": "Blue", + "prevo": "shinx", + "evos": [ + "luxray" + ], + "evoLevel": 15, + "eggGroups": [ + "Field" + ] + }, + "luxray": { + "num": 405, + "species": "Luxray", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 80, + "atk": 120, + "def": 79, + "spa": 95, + "spd": 79, + "spe": 70 + }, + "abilities": { + "0": "Rivalry", + "1": "Intimidate", + "H": "Guts" + }, + "heightm": 1.4, + "weightkg": 42, + "color": "Blue", + "prevo": "luxio", + "evoLevel": 30, + "eggGroups": [ + "Field" + ] + }, + "budew": { + "num": 406, + "species": "Budew", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 40, + "atk": 30, + "def": 35, + "spa": 50, + "spd": 70, + "spe": 55 + }, + "abilities": { + "0": "Natural Cure", + "1": "Poison Point", + "H": "Leaf Guard" + }, + "heightm": 0.2, + "weightkg": 1.2, + "color": "Green", + "evos": [ + "roselia" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "roserade": { + "num": 407, + "species": "Roserade", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 70, + "def": 65, + "spa": 125, + "spd": 105, + "spe": 90 + }, + "abilities": { + "0": "Natural Cure", + "1": "Poison Point", + "H": "Technician" + }, + "heightm": 0.9, + "weightkg": 14.5, + "color": "Green", + "prevo": "roselia", + "evoLevel": 1, + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "cranidos": { + "num": 408, + "species": "Cranidos", + "types": [ + "Rock" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 67, + "atk": 125, + "def": 40, + "spa": 30, + "spd": 30, + "spe": 58 + }, + "abilities": { + "0": "Mold Breaker", + "H": "Sheer Force" + }, + "heightm": 0.9, + "weightkg": 31.5, + "color": "Blue", + "evos": [ + "rampardos" + ], + "eggGroups": [ + "Monster" + ] + }, + "rampardos": { + "num": 409, + "species": "Rampardos", + "types": [ + "Rock" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 97, + "atk": 165, + "def": 60, + "spa": 65, + "spd": 50, + "spe": 58 + }, + "abilities": { + "0": "Mold Breaker", + "H": "Sheer Force" + }, + "heightm": 1.6, + "weightkg": 102.5, + "color": "Blue", + "prevo": "cranidos", + "evoLevel": 30, + "eggGroups": [ + "Monster" + ] + }, + "shieldon": { + "num": 410, + "species": "Shieldon", + "types": [ + "Rock", + "Steel" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 30, + "atk": 42, + "def": 118, + "spa": 42, + "spd": 88, + "spe": 30 + }, + "abilities": { + "0": "Sturdy", + "H": "Soundproof" + }, + "heightm": 0.5, + "weightkg": 57, + "color": "Gray", + "evos": [ + "bastiodon" + ], + "eggGroups": [ + "Monster" + ] + }, + "bastiodon": { + "num": 411, + "species": "Bastiodon", + "types": [ + "Rock", + "Steel" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 60, + "atk": 52, + "def": 168, + "spa": 47, + "spd": 138, + "spe": 30 + }, + "abilities": { + "0": "Sturdy", + "H": "Soundproof" + }, + "heightm": 1.3, + "weightkg": 149.5, + "color": "Gray", + "prevo": "shieldon", + "evoLevel": 30, + "eggGroups": [ + "Monster" + ] + }, + "burmy": { + "num": 412, + "species": "Burmy", + "baseForme": "Grass", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 40, + "atk": 29, + "def": 45, + "spa": 29, + "spd": 45, + "spe": 36 + }, + "abilities": { + "0": "Shed Skin", + "H": "Overcoat" + }, + "heightm": 0.2, + "weightkg": 3.4, + "color": "Gray", + "evos": [ + "wormadam", + "wormadamsandy", + "wormadamtrash", + "mothim" + ], + "eggGroups": [ + "Bug" + ], + "otherForms": [ + "burmysandy", + "burmytrash" + ] + }, + "wormadam": { + "num": 413, + "species": "Wormadam", + "baseForme": "Grass", + "types": [ + "Bug", + "Grass" + ], + "gender": "F", + "baseStats": { + "hp": 60, + "atk": 59, + "def": 85, + "spa": 79, + "spd": 105, + "spe": 36 + }, + "abilities": { + "0": "Anticipation", + "H": "Overcoat" + }, + "heightm": 0.5, + "weightkg": 6.5, + "color": "Gray", + "prevo": "burmy", + "evoLevel": 20, + "eggGroups": [ + "Bug" + ], + "otherFormes": [ + "wormadamsandy", + "wormadamtrash" + ] + }, + "wormadamsandy": { + "num": 413, + "species": "Wormadam-Sandy", + "baseSpecies": "Wormadam", + "forme": "Sandy", + "formeLetter": "G", + "types": [ + "Bug", + "Ground" + ], + "gender": "F", + "baseStats": { + "hp": 60, + "atk": 79, + "def": 105, + "spa": 59, + "spd": 85, + "spe": 36 + }, + "abilities": { + "0": "Anticipation", + "H": "Overcoat" + }, + "heightm": 0.5, + "weightkg": 6.5, + "color": "Gray", + "prevo": "burmy", + "evoLevel": 20, + "eggGroups": [ + "Bug" + ] + }, + "wormadamtrash": { + "num": 413, + "species": "Wormadam-Trash", + "baseSpecies": "Wormadam", + "forme": "Trash", + "formeLetter": "S", + "types": [ + "Bug", + "Steel" + ], + "gender": "F", + "baseStats": { + "hp": 60, + "atk": 69, + "def": 95, + "spa": 69, + "spd": 95, + "spe": 36 + }, + "abilities": { + "0": "Anticipation", + "H": "Overcoat" + }, + "heightm": 0.5, + "weightkg": 6.5, + "color": "Gray", + "prevo": "burmy", + "evoLevel": 20, + "eggGroups": [ + "Bug" + ] + }, + "mothim": { + "num": 414, + "species": "Mothim", + "types": [ + "Bug", + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 70, + "atk": 94, + "def": 50, + "spa": 94, + "spd": 50, + "spe": 66 + }, + "abilities": { + "0": "Swarm", + "H": "Tinted Lens" + }, + "heightm": 0.9, + "weightkg": 23.3, + "color": "Yellow", + "prevo": "burmy", + "evoLevel": 20, + "eggGroups": [ + "Bug" + ] + }, + "combee": { + "num": 415, + "species": "Combee", + "types": [ + "Bug", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 30, + "atk": 30, + "def": 42, + "spa": 30, + "spd": 42, + "spe": 70 + }, + "abilities": { + "0": "Honey Gather", + "H": "Hustle" + }, + "heightm": 0.3, + "weightkg": 5.5, + "color": "Yellow", + "evos": [ + "vespiquen" + ], + "eggGroups": [ + "Bug" + ] + }, + "vespiquen": { + "num": 416, + "species": "Vespiquen", + "types": [ + "Bug", + "Flying" + ], + "gender": "F", + "baseStats": { + "hp": 70, + "atk": 80, + "def": 102, + "spa": 80, + "spd": 102, + "spe": 40 + }, + "abilities": { + "0": "Pressure", + "H": "Unnerve" + }, + "heightm": 1.2, + "weightkg": 38.5, + "color": "Yellow", + "prevo": "combee", + "evoLevel": 21, + "eggGroups": [ + "Bug" + ] + }, + "pachirisu": { + "num": 417, + "species": "Pachirisu", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 60, + "atk": 45, + "def": 70, + "spa": 45, + "spd": 90, + "spe": 95 + }, + "abilities": { + "0": "Run Away", + "1": "Pickup", + "H": "Volt Absorb" + }, + "heightm": 0.4, + "weightkg": 3.9, + "color": "White", + "eggGroups": [ + "Field", + "Fairy" + ] + }, + "buizel": { + "num": 418, + "species": "Buizel", + "types": [ + "Water" + ], + "baseStats": { + "hp": 55, + "atk": 65, + "def": 35, + "spa": 60, + "spd": 30, + "spe": 85 + }, + "abilities": { + "0": "Swift Swim", + "H": "Water Veil" + }, + "heightm": 0.7, + "weightkg": 29.5, + "color": "Brown", + "evos": [ + "floatzel" + ], + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "floatzel": { + "num": 419, + "species": "Floatzel", + "types": [ + "Water" + ], + "baseStats": { + "hp": 85, + "atk": 105, + "def": 55, + "spa": 85, + "spd": 50, + "spe": 115 + }, + "abilities": { + "0": "Swift Swim", + "H": "Water Veil" + }, + "heightm": 1.1, + "weightkg": 33.5, + "color": "Brown", + "prevo": "buizel", + "evoLevel": 26, + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "cherubi": { + "num": 420, + "species": "Cherubi", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 45, + "atk": 35, + "def": 45, + "spa": 62, + "spd": 53, + "spe": 35 + }, + "abilities": { + "0": "Chlorophyll" + }, + "heightm": 0.4, + "weightkg": 3.3, + "color": "Pink", + "evos": [ + "cherrim" + ], + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "cherrim": { + "num": 421, + "species": "Cherrim", + "baseForme": "Overcast", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 70, + "atk": 60, + "def": 70, + "spa": 87, + "spd": 78, + "spe": 85 + }, + "abilities": { + "0": "Flower Gift" + }, + "heightm": 0.5, + "weightkg": 9.3, + "color": "Pink", + "prevo": "cherubi", + "evoLevel": 25, + "eggGroups": [ + "Fairy", + "Grass" + ], + "otherFormes": [ + "cherrimsunshine" + ] + }, + "cherrimsunshine": { + "num": 421, + "species": "Cherrim-Sunshine", + "baseSpecies": "Cherrim", + "forme": "Sunshine", + "formeLetter": "S", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 70, + "atk": 60, + "def": 70, + "spa": 87, + "spd": 78, + "spe": 85 + }, + "abilities": { + "0": "Flower Gift" + }, + "heightm": 0.5, + "weightkg": 9.3, + "color": "Pink", + "prevo": "cherubi", + "evoLevel": 25, + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "shellos": { + "num": 422, + "species": "Shellos", + "baseForme": "West", + "types": [ + "Water" + ], + "baseStats": { + "hp": 76, + "atk": 48, + "def": 48, + "spa": 57, + "spd": 62, + "spe": 34 + }, + "abilities": { + "0": "Sticky Hold", + "1": "Storm Drain", + "H": "Sand Force" + }, + "heightm": 0.3, + "weightkg": 6.3, + "color": "Purple", + "evos": [ + "gastrodon" + ], + "eggGroups": [ + "Water 1", + "Amorphous" + ], + "otherForms": [ + "shelloseast" + ] + }, + "gastrodon": { + "num": 423, + "species": "Gastrodon", + "baseForme": "West", + "types": [ + "Water", + "Ground" + ], + "baseStats": { + "hp": 111, + "atk": 83, + "def": 68, + "spa": 92, + "spd": 82, + "spe": 39 + }, + "abilities": { + "0": "Sticky Hold", + "1": "Storm Drain", + "H": "Sand Force" + }, + "heightm": 0.9, + "weightkg": 29.9, + "color": "Purple", + "prevo": "shellos", + "evoLevel": 30, + "eggGroups": [ + "Water 1", + "Amorphous" + ], + "otherForms": [ + "gastrodoneast" + ] + }, + "ambipom": { + "num": 424, + "species": "Ambipom", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 75, + "atk": 100, + "def": 66, + "spa": 60, + "spd": 66, + "spe": 115 + }, + "abilities": { + "0": "Technician", + "1": "Pickup", + "H": "Skill Link" + }, + "heightm": 1.2, + "weightkg": 20.3, + "color": "Purple", + "prevo": "aipom", + "evoLevel": 2, + "evoMove": "Double Hit", + "eggGroups": [ + "Field" + ] + }, + "drifloon": { + "num": 425, + "species": "Drifloon", + "types": [ + "Ghost", + "Flying" + ], + "baseStats": { + "hp": 90, + "atk": 50, + "def": 34, + "spa": 60, + "spd": 44, + "spe": 70 + }, + "abilities": { + "0": "Aftermath", + "1": "Unburden", + "H": "Flare Boost" + }, + "heightm": 0.4, + "weightkg": 1.2, + "color": "Purple", + "evos": [ + "drifblim" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "drifblim": { + "num": 426, + "species": "Drifblim", + "types": [ + "Ghost", + "Flying" + ], + "baseStats": { + "hp": 150, + "atk": 80, + "def": 44, + "spa": 90, + "spd": 54, + "spe": 80 + }, + "abilities": { + "0": "Aftermath", + "1": "Unburden", + "H": "Flare Boost" + }, + "heightm": 1.2, + "weightkg": 15, + "color": "Purple", + "prevo": "drifloon", + "evoLevel": 28, + "eggGroups": [ + "Amorphous" + ] + }, + "buneary": { + "num": 427, + "species": "Buneary", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 55, + "atk": 66, + "def": 44, + "spa": 44, + "spd": 56, + "spe": 85 + }, + "abilities": { + "0": "Run Away", + "1": "Klutz", + "H": "Limber" + }, + "heightm": 0.4, + "weightkg": 5.5, + "color": "Brown", + "evos": [ + "lopunny" + ], + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "lopunny": { + "num": 428, + "species": "Lopunny", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 65, + "atk": 76, + "def": 84, + "spa": 54, + "spd": 96, + "spe": 105 + }, + "abilities": { + "0": "Cute Charm", + "1": "Klutz", + "H": "Limber" + }, + "heightm": 1.2, + "weightkg": 33.3, + "color": "Brown", + "prevo": "buneary", + "evoLevel": 2, + "eggGroups": [ + "Field", + "Human-Like" + ], + "otherFormes": [ + "lopunnymega" + ] + }, + "lopunnymega": { + "num": 428, + "species": "Lopunny-Mega", + "baseSpecies": "Lopunny", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Normal", + "Fighting" + ], + "baseStats": { + "hp": 65, + "atk": 136, + "def": 94, + "spa": 54, + "spd": 96, + "spe": 135 + }, + "abilities": { + "0": "Scrappy" + }, + "heightm": 1.3, + "weightkg": 28.3, + "color": "Brown", + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "mismagius": { + "num": 429, + "species": "Mismagius", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 60, + "atk": 60, + "def": 60, + "spa": 105, + "spd": 105, + "spe": 105 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.9, + "weightkg": 4.4, + "color": "Purple", + "prevo": "misdreavus", + "evoLevel": 1, + "eggGroups": [ + "Amorphous" + ] + }, + "honchkrow": { + "num": 430, + "species": "Honchkrow", + "types": [ + "Dark", + "Flying" + ], + "baseStats": { + "hp": 100, + "atk": 125, + "def": 52, + "spa": 105, + "spd": 52, + "spe": 71 + }, + "abilities": { + "0": "Insomnia", + "1": "Super Luck", + "H": "Moxie" + }, + "heightm": 0.9, + "weightkg": 27.3, + "color": "Black", + "prevo": "murkrow", + "evoLevel": 1, + "eggGroups": [ + "Flying" + ] + }, + "glameow": { + "num": 431, + "species": "Glameow", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 49, + "atk": 55, + "def": 42, + "spa": 42, + "spd": 37, + "spe": 85 + }, + "abilities": { + "0": "Limber", + "1": "Own Tempo", + "H": "Keen Eye" + }, + "heightm": 0.5, + "weightkg": 3.9, + "color": "Gray", + "evos": [ + "purugly" + ], + "eggGroups": [ + "Field" + ] + }, + "purugly": { + "num": 432, + "species": "Purugly", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 71, + "atk": 82, + "def": 64, + "spa": 64, + "spd": 59, + "spe": 112 + }, + "abilities": { + "0": "Thick Fat", + "1": "Own Tempo", + "H": "Defiant" + }, + "heightm": 1, + "weightkg": 43.8, + "color": "Gray", + "prevo": "glameow", + "evoLevel": 38, + "eggGroups": [ + "Field" + ] + }, + "chingling": { + "num": 433, + "species": "Chingling", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 45, + "atk": 30, + "def": 50, + "spa": 65, + "spd": 50, + "spe": 45 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.2, + "weightkg": 0.6, + "color": "Yellow", + "evos": [ + "chimecho" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "stunky": { + "num": 434, + "species": "Stunky", + "types": [ + "Poison", + "Dark" + ], + "baseStats": { + "hp": 63, + "atk": 63, + "def": 47, + "spa": 41, + "spd": 41, + "spe": 74 + }, + "abilities": { + "0": "Stench", + "1": "Aftermath", + "H": "Keen Eye" + }, + "heightm": 0.4, + "weightkg": 19.2, + "color": "Purple", + "evos": [ + "skuntank" + ], + "eggGroups": [ + "Field" + ] + }, + "skuntank": { + "num": 435, + "species": "Skuntank", + "types": [ + "Poison", + "Dark" + ], + "baseStats": { + "hp": 103, + "atk": 93, + "def": 67, + "spa": 71, + "spd": 61, + "spe": 84 + }, + "abilities": { + "0": "Stench", + "1": "Aftermath", + "H": "Keen Eye" + }, + "heightm": 1, + "weightkg": 38, + "color": "Purple", + "prevo": "stunky", + "evoLevel": 34, + "eggGroups": [ + "Field" + ] + }, + "bronzor": { + "num": 436, + "species": "Bronzor", + "types": [ + "Steel", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 57, + "atk": 24, + "def": 86, + "spa": 24, + "spd": 86, + "spe": 23 + }, + "abilities": { + "0": "Levitate", + "1": "Heatproof", + "H": "Heavy Metal" + }, + "heightm": 0.5, + "weightkg": 60.5, + "color": "Green", + "evos": [ + "bronzong" + ], + "eggGroups": [ + "Mineral" + ] + }, + "bronzong": { + "num": 437, + "species": "Bronzong", + "types": [ + "Steel", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 67, + "atk": 89, + "def": 116, + "spa": 79, + "spd": 116, + "spe": 33 + }, + "abilities": { + "0": "Levitate", + "1": "Heatproof", + "H": "Heavy Metal" + }, + "heightm": 1.3, + "weightkg": 187, + "color": "Green", + "prevo": "bronzor", + "evoLevel": 33, + "eggGroups": [ + "Mineral" + ] + }, + "bonsly": { + "num": 438, + "species": "Bonsly", + "types": [ + "Rock" + ], + "baseStats": { + "hp": 50, + "atk": 80, + "def": 95, + "spa": 10, + "spd": 45, + "spe": 10 + }, + "abilities": { + "0": "Sturdy", + "1": "Rock Head", + "H": "Rattled" + }, + "heightm": 0.5, + "weightkg": 15, + "color": "Brown", + "evos": [ + "sudowoodo" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "mimejr": { + "num": 439, + "species": "Mime Jr.", + "types": [ + "Psychic", + "Fairy" + ], + "baseStats": { + "hp": 20, + "atk": 25, + "def": 45, + "spa": 70, + "spd": 90, + "spe": 60 + }, + "abilities": { + "0": "Soundproof", + "1": "Filter", + "H": "Technician" + }, + "heightm": 0.6, + "weightkg": 13, + "color": "Pink", + "evos": [ + "mrmime" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "happiny": { + "num": 440, + "species": "Happiny", + "types": [ + "Normal" + ], + "gender": "F", + "baseStats": { + "hp": 100, + "atk": 5, + "def": 5, + "spa": 15, + "spd": 65, + "spe": 30 + }, + "abilities": { + "0": "Natural Cure", + "1": "Serene Grace", + "H": "Friend Guard" + }, + "heightm": 0.6, + "weightkg": 24.4, + "color": "Pink", + "evos": [ + "chansey" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "chatot": { + "num": 441, + "species": "Chatot", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 76, + "atk": 65, + "def": 45, + "spa": 92, + "spd": 42, + "spe": 91 + }, + "abilities": { + "0": "Keen Eye", + "1": "Tangled Feet", + "H": "Big Pecks" + }, + "heightm": 0.5, + "weightkg": 1.9, + "color": "Black", + "eggGroups": [ + "Flying" + ] + }, + "spiritomb": { + "num": 442, + "species": "Spiritomb", + "types": [ + "Ghost", + "Dark" + ], + "baseStats": { + "hp": 50, + "atk": 92, + "def": 108, + "spa": 92, + "spd": 108, + "spe": 35 + }, + "abilities": { + "0": "Pressure", + "H": "Infiltrator" + }, + "heightm": 1, + "weightkg": 108, + "color": "Purple", + "eggGroups": [ + "Amorphous" + ] + }, + "gible": { + "num": 443, + "species": "Gible", + "types": [ + "Dragon", + "Ground" + ], + "baseStats": { + "hp": 58, + "atk": 70, + "def": 45, + "spa": 40, + "spd": 45, + "spe": 42 + }, + "abilities": { + "0": "Sand Veil", + "H": "Rough Skin" + }, + "heightm": 0.7, + "weightkg": 20.5, + "color": "Blue", + "evos": [ + "gabite" + ], + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "gabite": { + "num": 444, + "species": "Gabite", + "types": [ + "Dragon", + "Ground" + ], + "baseStats": { + "hp": 68, + "atk": 90, + "def": 65, + "spa": 50, + "spd": 55, + "spe": 82 + }, + "abilities": { + "0": "Sand Veil", + "H": "Rough Skin" + }, + "heightm": 1.4, + "weightkg": 56, + "color": "Blue", + "prevo": "gible", + "evos": [ + "garchomp" + ], + "evoLevel": 24, + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "garchomp": { + "num": 445, + "species": "Garchomp", + "types": [ + "Dragon", + "Ground" + ], + "baseStats": { + "hp": 108, + "atk": 130, + "def": 95, + "spa": 80, + "spd": 85, + "spe": 102 + }, + "abilities": { + "0": "Sand Veil", + "H": "Rough Skin" + }, + "heightm": 1.9, + "weightkg": 95, + "color": "Blue", + "prevo": "gabite", + "evoLevel": 48, + "eggGroups": [ + "Monster", + "Dragon" + ], + "otherFormes": [ + "garchompmega" + ] + }, + "garchompmega": { + "num": 445, + "species": "Garchomp-Mega", + "baseSpecies": "Garchomp", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Dragon", + "Ground" + ], + "baseStats": { + "hp": 108, + "atk": 170, + "def": 115, + "spa": 120, + "spd": 95, + "spe": 92 + }, + "abilities": { + "0": "Sand Force" + }, + "heightm": 1.9, + "weightkg": 95, + "color": "Blue", + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "munchlax": { + "num": 446, + "species": "Munchlax", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 135, + "atk": 85, + "def": 40, + "spa": 40, + "spd": 85, + "spe": 5 + }, + "abilities": { + "0": "Pickup", + "1": "Thick Fat", + "H": "Gluttony" + }, + "heightm": 0.6, + "weightkg": 105, + "color": "Black", + "evos": [ + "snorlax" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "riolu": { + "num": 447, + "species": "Riolu", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 40, + "atk": 70, + "def": 40, + "spa": 35, + "spd": 40, + "spe": 60 + }, + "abilities": { + "0": "Steadfast", + "1": "Inner Focus", + "H": "Prankster" + }, + "heightm": 0.7, + "weightkg": 20.2, + "color": "Blue", + "evos": [ + "lucario" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "lucario": { + "num": 448, + "species": "Lucario", + "types": [ + "Fighting", + "Steel" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 70, + "atk": 110, + "def": 70, + "spa": 115, + "spd": 70, + "spe": 90 + }, + "abilities": { + "0": "Steadfast", + "1": "Inner Focus", + "H": "Justified" + }, + "heightm": 1.2, + "weightkg": 54, + "color": "Blue", + "prevo": "riolu", + "evoLevel": 2, + "eggGroups": [ + "Field", + "Human-Like" + ], + "otherFormes": [ + "lucariomega" + ] + }, + "lucariomega": { + "num": 448, + "species": "Lucario-Mega", + "baseSpecies": "Lucario", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Fighting", + "Steel" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 70, + "atk": 145, + "def": 88, + "spa": 140, + "spd": 70, + "spe": 112 + }, + "abilities": { + "0": "Adaptability" + }, + "heightm": 1.3, + "weightkg": 57.5, + "color": "Blue", + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "hippopotas": { + "num": 449, + "species": "Hippopotas", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 68, + "atk": 72, + "def": 78, + "spa": 38, + "spd": 42, + "spe": 32 + }, + "abilities": { + "0": "Sand Stream", + "H": "Sand Force" + }, + "heightm": 0.8, + "weightkg": 49.5, + "color": "Brown", + "evos": [ + "hippowdon" + ], + "eggGroups": [ + "Field" + ] + }, + "hippowdon": { + "num": 450, + "species": "Hippowdon", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 108, + "atk": 112, + "def": 118, + "spa": 68, + "spd": 72, + "spe": 47 + }, + "abilities": { + "0": "Sand Stream", + "H": "Sand Force" + }, + "heightm": 2, + "weightkg": 300, + "color": "Brown", + "prevo": "hippopotas", + "evoLevel": 34, + "eggGroups": [ + "Field" + ] + }, + "skorupi": { + "num": 451, + "species": "Skorupi", + "types": [ + "Poison", + "Bug" + ], + "baseStats": { + "hp": 40, + "atk": 50, + "def": 90, + "spa": 30, + "spd": 55, + "spe": 65 + }, + "abilities": { + "0": "Battle Armor", + "1": "Sniper", + "H": "Keen Eye" + }, + "heightm": 0.8, + "weightkg": 12, + "color": "Purple", + "evos": [ + "drapion" + ], + "eggGroups": [ + "Bug", + "Water 3" + ] + }, + "drapion": { + "num": 452, + "species": "Drapion", + "types": [ + "Poison", + "Dark" + ], + "baseStats": { + "hp": 70, + "atk": 90, + "def": 110, + "spa": 60, + "spd": 75, + "spe": 95 + }, + "abilities": { + "0": "Battle Armor", + "1": "Sniper", + "H": "Keen Eye" + }, + "heightm": 1.3, + "weightkg": 61.5, + "color": "Purple", + "prevo": "skorupi", + "evoLevel": 40, + "eggGroups": [ + "Bug", + "Water 3" + ] + }, + "croagunk": { + "num": 453, + "species": "Croagunk", + "types": [ + "Poison", + "Fighting" + ], + "baseStats": { + "hp": 48, + "atk": 61, + "def": 40, + "spa": 61, + "spd": 40, + "spe": 50 + }, + "abilities": { + "0": "Anticipation", + "1": "Dry Skin", + "H": "Poison Touch" + }, + "heightm": 0.7, + "weightkg": 23, + "color": "Blue", + "evos": [ + "toxicroak" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "toxicroak": { + "num": 454, + "species": "Toxicroak", + "types": [ + "Poison", + "Fighting" + ], + "baseStats": { + "hp": 83, + "atk": 106, + "def": 65, + "spa": 86, + "spd": 65, + "spe": 85 + }, + "abilities": { + "0": "Anticipation", + "1": "Dry Skin", + "H": "Poison Touch" + }, + "heightm": 1.3, + "weightkg": 44.4, + "color": "Blue", + "prevo": "croagunk", + "evoLevel": 37, + "eggGroups": [ + "Human-Like" + ] + }, + "carnivine": { + "num": 455, + "species": "Carnivine", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 74, + "atk": 100, + "def": 72, + "spa": 90, + "spd": 72, + "spe": 46 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.4, + "weightkg": 27, + "color": "Green", + "eggGroups": [ + "Grass" + ] + }, + "finneon": { + "num": 456, + "species": "Finneon", + "types": [ + "Water" + ], + "baseStats": { + "hp": 49, + "atk": 49, + "def": 56, + "spa": 49, + "spd": 61, + "spe": 66 + }, + "abilities": { + "0": "Swift Swim", + "1": "Storm Drain", + "H": "Water Veil" + }, + "heightm": 0.4, + "weightkg": 7, + "color": "Blue", + "evos": [ + "lumineon" + ], + "eggGroups": [ + "Water 2" + ] + }, + "lumineon": { + "num": 457, + "species": "Lumineon", + "types": [ + "Water" + ], + "baseStats": { + "hp": 69, + "atk": 69, + "def": 76, + "spa": 69, + "spd": 86, + "spe": 91 + }, + "abilities": { + "0": "Swift Swim", + "1": "Storm Drain", + "H": "Water Veil" + }, + "heightm": 1.2, + "weightkg": 24, + "color": "Blue", + "prevo": "finneon", + "evoLevel": 31, + "eggGroups": [ + "Water 2" + ] + }, + "mantyke": { + "num": 458, + "species": "Mantyke", + "types": [ + "Water", + "Flying" + ], + "baseStats": { + "hp": 45, + "atk": 20, + "def": 50, + "spa": 60, + "spd": 120, + "spe": 50 + }, + "abilities": { + "0": "Swift Swim", + "1": "Water Absorb", + "H": "Water Veil" + }, + "heightm": 1, + "weightkg": 65, + "color": "Blue", + "evos": [ + "mantine" + ], + "eggGroups": [ + "Undiscovered" + ] + }, + "snover": { + "num": 459, + "species": "Snover", + "types": [ + "Grass", + "Ice" + ], + "baseStats": { + "hp": 60, + "atk": 62, + "def": 50, + "spa": 62, + "spd": 60, + "spe": 40 + }, + "abilities": { + "0": "Snow Warning", + "H": "Soundproof" + }, + "heightm": 1, + "weightkg": 50.5, + "color": "White", + "evos": [ + "abomasnow" + ], + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "abomasnow": { + "num": 460, + "species": "Abomasnow", + "types": [ + "Grass", + "Ice" + ], + "baseStats": { + "hp": 90, + "atk": 92, + "def": 75, + "spa": 92, + "spd": 85, + "spe": 60 + }, + "abilities": { + "0": "Snow Warning", + "H": "Soundproof" + }, + "heightm": 2.2, + "weightkg": 135.5, + "color": "White", + "prevo": "snover", + "evoLevel": 40, + "eggGroups": [ + "Monster", + "Grass" + ], + "otherFormes": [ + "abomasnowmega" + ] + }, + "abomasnowmega": { + "num": 460, + "species": "Abomasnow-Mega", + "baseSpecies": "Abomasnow", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Grass", + "Ice" + ], + "baseStats": { + "hp": 90, + "atk": 132, + "def": 105, + "spa": 132, + "spd": 105, + "spe": 30 + }, + "abilities": { + "0": "Snow Warning" + }, + "heightm": 2.7, + "weightkg": 185, + "color": "White", + "eggGroups": [ + "Monster", + "Grass" + ] + }, + "weavile": { + "num": 461, + "species": "Weavile", + "types": [ + "Dark", + "Ice" + ], + "baseStats": { + "hp": 70, + "atk": 120, + "def": 65, + "spa": 45, + "spd": 85, + "spe": 125 + }, + "abilities": { + "0": "Pressure", + "H": "Pickpocket" + }, + "heightm": 1.1, + "weightkg": 34, + "color": "Black", + "prevo": "sneasel", + "evoLevel": 2, + "eggGroups": [ + "Field" + ] + }, + "magnezone": { + "num": 462, + "species": "Magnezone", + "types": [ + "Electric", + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 70, + "atk": 70, + "def": 115, + "spa": 130, + "spd": 90, + "spe": 60 + }, + "abilities": { + "0": "Magnet Pull", + "1": "Sturdy", + "H": "Analytic" + }, + "heightm": 1.2, + "weightkg": 180, + "color": "Gray", + "prevo": "magneton", + "evoLevel": 31, + "eggGroups": [ + "Mineral" + ] + }, + "lickilicky": { + "num": 463, + "species": "Lickilicky", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 110, + "atk": 85, + "def": 95, + "spa": 80, + "spd": 95, + "spe": 50 + }, + "abilities": { + "0": "Own Tempo", + "1": "Oblivious", + "H": "Cloud Nine" + }, + "heightm": 1.7, + "weightkg": 140, + "color": "Pink", + "prevo": "lickitung", + "evoLevel": 2, + "evoMove": "Rollout", + "eggGroups": [ + "Monster" + ] + }, + "rhyperior": { + "num": 464, + "species": "Rhyperior", + "types": [ + "Ground", + "Rock" + ], + "baseStats": { + "hp": 115, + "atk": 140, + "def": 130, + "spa": 55, + "spd": 55, + "spe": 40 + }, + "abilities": { + "0": "Lightning Rod", + "1": "Solid Rock", + "H": "Reckless" + }, + "heightm": 2.4, + "weightkg": 282.8, + "color": "Gray", + "prevo": "rhydon", + "evoLevel": 42, + "eggGroups": [ + "Monster", + "Field" + ] + }, + "tangrowth": { + "num": 465, + "species": "Tangrowth", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 100, + "atk": 100, + "def": 125, + "spa": 110, + "spd": 50, + "spe": 50 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Leaf Guard", + "H": "Regenerator" + }, + "heightm": 2, + "weightkg": 128.6, + "color": "Blue", + "prevo": "tangela", + "evoLevel": 2, + "evoMove": "AncientPower", + "eggGroups": [ + "Grass" + ] + }, + "electivire": { + "num": 466, + "species": "Electivire", + "types": [ + "Electric" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 75, + "atk": 123, + "def": 67, + "spa": 95, + "spd": 85, + "spe": 95 + }, + "abilities": { + "0": "Motor Drive", + "H": "Vital Spirit" + }, + "heightm": 1.8, + "weightkg": 138.6, + "color": "Yellow", + "prevo": "electabuzz", + "evoLevel": 30, + "eggGroups": [ + "Human-Like" + ] + }, + "magmortar": { + "num": 467, + "species": "Magmortar", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 75, + "atk": 95, + "def": 67, + "spa": 125, + "spd": 95, + "spe": 83 + }, + "abilities": { + "0": "Flame Body", + "H": "Vital Spirit" + }, + "heightm": 1.6, + "weightkg": 68, + "color": "Red", + "prevo": "magmar", + "evoLevel": 30, + "eggGroups": [ + "Human-Like" + ] + }, + "togekiss": { + "num": 468, + "species": "Togekiss", + "types": [ + "Fairy", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 85, + "atk": 50, + "def": 95, + "spa": 120, + "spd": 115, + "spe": 80 + }, + "abilities": { + "0": "Hustle", + "1": "Serene Grace", + "H": "Super Luck" + }, + "heightm": 1.5, + "weightkg": 38, + "color": "White", + "prevo": "togetic", + "evoLevel": 2, + "eggGroups": [ + "Flying", + "Fairy" + ] + }, + "yanmega": { + "num": 469, + "species": "Yanmega", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 86, + "atk": 76, + "def": 86, + "spa": 116, + "spd": 56, + "spe": 95 + }, + "abilities": { + "0": "Speed Boost", + "1": "Tinted Lens", + "H": "Frisk" + }, + "heightm": 1.9, + "weightkg": 51.5, + "color": "Green", + "prevo": "yanma", + "evoLevel": 2, + "evoMove": "AncientPower", + "eggGroups": [ + "Bug" + ] + }, + "leafeon": { + "num": 470, + "species": "Leafeon", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 65, + "atk": 110, + "def": 130, + "spa": 60, + "spd": 65, + "spe": 95 + }, + "abilities": { + "0": "Leaf Guard", + "H": "Chlorophyll" + }, + "heightm": 1, + "weightkg": 25.5, + "color": "Green", + "prevo": "eevee", + "evoLevel": 2, + "eggGroups": [ + "Field" + ] + }, + "glaceon": { + "num": 471, + "species": "Glaceon", + "types": [ + "Ice" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 65, + "atk": 60, + "def": 110, + "spa": 130, + "spd": 95, + "spe": 65 + }, + "abilities": { + "0": "Snow Cloak", + "H": "Ice Body" + }, + "heightm": 0.8, + "weightkg": 25.9, + "color": "Blue", + "prevo": "eevee", + "evoLevel": 2, + "eggGroups": [ + "Field" + ] + }, + "gliscor": { + "num": 472, + "species": "Gliscor", + "types": [ + "Ground", + "Flying" + ], + "baseStats": { + "hp": 75, + "atk": 95, + "def": 125, + "spa": 45, + "spd": 75, + "spe": 95 + }, + "abilities": { + "0": "Hyper Cutter", + "1": "Sand Veil", + "H": "Poison Heal" + }, + "heightm": 2, + "weightkg": 42.5, + "color": "Purple", + "prevo": "gligar", + "evoLevel": 2, + "eggGroups": [ + "Bug" + ] + }, + "mamoswine": { + "num": 473, + "species": "Mamoswine", + "types": [ + "Ice", + "Ground" + ], + "baseStats": { + "hp": 110, + "atk": 130, + "def": 80, + "spa": 70, + "spd": 60, + "spe": 80 + }, + "abilities": { + "0": "Oblivious", + "1": "Snow Cloak", + "H": "Thick Fat" + }, + "heightm": 2.5, + "weightkg": 291, + "color": "Brown", + "prevo": "piloswine", + "evoLevel": 34, + "evoMove": "AncientPower", + "eggGroups": [ + "Field" + ] + }, + "porygonz": { + "num": 474, + "species": "Porygon-Z", + "types": [ + "Normal" + ], + "gender": "N", + "baseStats": { + "hp": 85, + "atk": 80, + "def": 70, + "spa": 135, + "spd": 75, + "spe": 90 + }, + "abilities": { + "0": "Adaptability", + "1": "Download", + "H": "Analytic" + }, + "heightm": 0.9, + "weightkg": 34, + "color": "Red", + "prevo": "porygon2", + "evoLevel": 1, + "eggGroups": [ + "Mineral" + ] + }, + "gallade": { + "num": 475, + "species": "Gallade", + "types": [ + "Psychic", + "Fighting" + ], + "gender": "M", + "baseStats": { + "hp": 68, + "atk": 125, + "def": 65, + "spa": 65, + "spd": 115, + "spe": 80 + }, + "abilities": { + "0": "Steadfast", + "H": "Justified" + }, + "heightm": 1.6, + "weightkg": 52, + "color": "White", + "prevo": "kirlia", + "evoLevel": 20, + "eggGroups": [ + "Amorphous" + ], + "otherFormes": [ + "gallademega" + ] + }, + "gallademega": { + "num": 475, + "species": "Gallade-Mega", + "baseSpecies": "Gallade", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Psychic", + "Fighting" + ], + "gender": "M", + "baseStats": { + "hp": 68, + "atk": 165, + "def": 95, + "spa": 65, + "spd": 115, + "spe": 110 + }, + "abilities": { + "0": "Inner Focus" + }, + "heightm": 1.6, + "weightkg": 56.4, + "color": "White", + "eggGroups": [ + "Amorphous" + ] + }, + "probopass": { + "num": 476, + "species": "Probopass", + "types": [ + "Rock", + "Steel" + ], + "baseStats": { + "hp": 60, + "atk": 55, + "def": 145, + "spa": 75, + "spd": 150, + "spe": 40 + }, + "abilities": { + "0": "Sturdy", + "1": "Magnet Pull", + "H": "Sand Force" + }, + "heightm": 1.4, + "weightkg": 340, + "color": "Gray", + "prevo": "nosepass", + "evoLevel": 2, + "eggGroups": [ + "Mineral" + ] + }, + "dusknoir": { + "num": 477, + "species": "Dusknoir", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 45, + "atk": 100, + "def": 135, + "spa": 65, + "spd": 135, + "spe": 45 + }, + "abilities": { + "0": "Pressure", + "H": "Frisk" + }, + "heightm": 2.2, + "weightkg": 106.6, + "color": "Black", + "prevo": "dusclops", + "evoLevel": 37, + "eggGroups": [ + "Amorphous" + ] + }, + "froslass": { + "num": 478, + "species": "Froslass", + "types": [ + "Ice", + "Ghost" + ], + "gender": "F", + "baseStats": { + "hp": 70, + "atk": 80, + "def": 70, + "spa": 80, + "spd": 70, + "spe": 110 + }, + "abilities": { + "0": "Snow Cloak", + "H": "Cursed Body" + }, + "heightm": 1.3, + "weightkg": 26.6, + "color": "White", + "prevo": "snorunt", + "evoLevel": 1, + "eggGroups": [ + "Fairy", + "Mineral" + ] + }, + "rotom": { + "num": 479, + "species": "Rotom", + "types": [ + "Electric", + "Ghost" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 50, + "def": 77, + "spa": 95, + "spd": 77, + "spe": 91 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Red", + "eggGroups": [ + "Amorphous" + ], + "otherFormes": [ + "rotomheat", + "rotomwash", + "rotomfrost", + "rotomfan", + "rotommow" + ] + }, + "rotomheat": { + "num": 479, + "species": "Rotom-Heat", + "baseSpecies": "Rotom", + "forme": "Heat", + "formeLetter": "H", + "types": [ + "Electric", + "Fire" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 65, + "def": 107, + "spa": 105, + "spd": 107, + "spe": 86 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Red", + "eggGroups": [ + "Amorphous" + ] + }, + "rotomwash": { + "num": 479, + "species": "Rotom-Wash", + "baseSpecies": "Rotom", + "forme": "Wash", + "formeLetter": "W", + "types": [ + "Electric", + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 65, + "def": 107, + "spa": 105, + "spd": 107, + "spe": 86 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Red", + "eggGroups": [ + "Amorphous" + ] + }, + "rotomfrost": { + "num": 479, + "species": "Rotom-Frost", + "baseSpecies": "Rotom", + "forme": "Frost", + "formeLetter": "F", + "types": [ + "Electric", + "Ice" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 65, + "def": 107, + "spa": 105, + "spd": 107, + "spe": 86 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Red", + "eggGroups": [ + "Amorphous" + ] + }, + "rotomfan": { + "num": 479, + "species": "Rotom-Fan", + "baseSpecies": "Rotom", + "forme": "Fan", + "formeLetter": "S", + "types": [ + "Electric", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 65, + "def": 107, + "spa": 105, + "spd": 107, + "spe": 86 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Red", + "eggGroups": [ + "Amorphous" + ] + }, + "rotommow": { + "num": 479, + "species": "Rotom-Mow", + "baseSpecies": "Rotom", + "forme": "Mow", + "formeLetter": "C", + "types": [ + "Electric", + "Grass" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 65, + "def": 107, + "spa": 105, + "spd": 107, + "spe": 86 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Red", + "eggGroups": [ + "Amorphous" + ] + }, + "uxie": { + "num": 480, + "species": "Uxie", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 75, + "atk": 75, + "def": 130, + "spa": 75, + "spd": 130, + "spe": 95 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "mesprit": { + "num": 481, + "species": "Mesprit", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 105, + "def": 105, + "spa": 105, + "spd": 105, + "spe": 80 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Pink", + "eggGroups": [ + "Undiscovered" + ] + }, + "azelf": { + "num": 482, + "species": "Azelf", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 75, + "atk": 125, + "def": 70, + "spa": 125, + "spd": 70, + "spe": 115 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.3, + "weightkg": 0.3, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "dialga": { + "num": 483, + "species": "Dialga", + "types": [ + "Steel", + "Dragon" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 120, + "def": 120, + "spa": 150, + "spd": 100, + "spe": 90 + }, + "abilities": { + "0": "Pressure", + "H": "Telepathy" + }, + "heightm": 5.4, + "weightkg": 683, + "color": "White", + "eggGroups": [ + "Undiscovered" + ] + }, + "palkia": { + "num": 484, + "species": "Palkia", + "types": [ + "Water", + "Dragon" + ], + "gender": "N", + "baseStats": { + "hp": 90, + "atk": 120, + "def": 100, + "spa": 150, + "spd": 120, + "spe": 100 + }, + "abilities": { + "0": "Pressure", + "H": "Telepathy" + }, + "heightm": 4.2, + "weightkg": 336, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ] + }, + "heatran": { + "num": 485, + "species": "Heatran", + "types": [ + "Fire", + "Steel" + ], + "baseStats": { + "hp": 91, + "atk": 90, + "def": 106, + "spa": 130, + "spd": 106, + "spe": 77 + }, + "abilities": { + "0": "Flash Fire", + "H": "Flame Body" + }, + "heightm": 1.7, + "weightkg": 430, + "color": "Brown", + "eggGroups": [ + "Undiscovered" + ] + }, + "regigigas": { + "num": 486, + "species": "Regigigas", + "types": [ + "Normal" + ], + "gender": "N", + "baseStats": { + "hp": 110, + "atk": 160, + "def": 110, + "spa": 80, + "spd": 110, + "spe": 100 + }, + "abilities": { + "0": "Slow Start" + }, + "heightm": 3.7, + "weightkg": 420, + "color": "White", + "eggGroups": [ + "Undiscovered" + ] + }, + "giratina": { + "num": 487, + "species": "Giratina", + "baseForme": "Altered", + "types": [ + "Ghost", + "Dragon" + ], + "gender": "N", + "baseStats": { + "hp": 150, + "atk": 100, + "def": 120, + "spa": 100, + "spd": 120, + "spe": 90 + }, + "abilities": { + "0": "Pressure", + "H": "Telepathy" + }, + "heightm": 4.5, + "weightkg": 750, + "color": "Black", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "giratinaorigin" + ] + }, + "giratinaorigin": { + "num": 487, + "species": "Giratina-Origin", + "baseSpecies": "Giratina", + "forme": "Origin", + "formeLetter": "O", + "types": [ + "Ghost", + "Dragon" + ], + "gender": "N", + "baseStats": { + "hp": 150, + "atk": 120, + "def": 100, + "spa": 120, + "spd": 100, + "spe": 90 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 6.9, + "weightkg": 650, + "color": "Black", + "eggGroups": [ + "Undiscovered" + ] + }, + "cresselia": { + "num": 488, + "species": "Cresselia", + "types": [ + "Psychic" + ], + "gender": "F", + "baseStats": { + "hp": 120, + "atk": 70, + "def": 120, + "spa": 75, + "spd": 130, + "spe": 85 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.5, + "weightkg": 85.6, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "phione": { + "num": 489, + "species": "Phione", + "types": [ + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 80, + "def": 80, + "spa": 80, + "spd": 80, + "spe": 80 + }, + "abilities": { + "0": "Hydration" + }, + "heightm": 0.4, + "weightkg": 3.1, + "color": "Blue", + "eggGroups": [ + "Water 1", + "Fairy" + ] + }, + "manaphy": { + "num": 490, + "species": "Manaphy", + "types": [ + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 100, + "def": 100, + "spa": 100, + "spd": 100, + "spe": 100 + }, + "abilities": { + "0": "Hydration" + }, + "heightm": 0.3, + "weightkg": 1.4, + "color": "Blue", + "eggGroups": [ + "Water 1", + "Fairy" + ] + }, + "darkrai": { + "num": 491, + "species": "Darkrai", + "types": [ + "Dark" + ], + "gender": "N", + "baseStats": { + "hp": 70, + "atk": 90, + "def": 90, + "spa": 135, + "spd": 90, + "spe": 125 + }, + "abilities": { + "0": "Bad Dreams" + }, + "heightm": 1.5, + "weightkg": 50.5, + "color": "Black", + "eggGroups": [ + "Undiscovered" + ] + }, + "shaymin": { + "num": 492, + "species": "Shaymin", + "baseForme": "Land", + "types": [ + "Grass" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 100, + "def": 100, + "spa": 100, + "spd": 100, + "spe": 100 + }, + "abilities": { + "0": "Natural Cure" + }, + "heightm": 0.2, + "weightkg": 2.1, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "shayminsky" + ] + }, + "shayminsky": { + "num": 492, + "species": "Shaymin-Sky", + "baseSpecies": "Shaymin", + "forme": "Sky", + "formeLetter": "S", + "types": [ + "Grass", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 103, + "def": 75, + "spa": 120, + "spd": 75, + "spe": 127 + }, + "abilities": { + "0": "Serene Grace" + }, + "heightm": 0.4, + "weightkg": 5.2, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceus": { + "num": 493, + "species": "Arceus", + "baseForme": "Normal", + "types": [ + "Normal" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "arceusbug", + "arceusdark", + "arceusdragon", + "arceuselectric", + "arceusfairy", + "arceusfighting", + "arceusfire", + "arceusflying", + "arceusghost", + "arceusgrass", + "arceusground", + "arceusice", + "arceuspoison", + "arceuspsychic", + "arceusrock", + "arceussteel", + "arceuswater" + ] + }, + "arceusbug": { + "num": 493, + "species": "Arceus-Bug", + "baseSpecies": "Arceus", + "forme": "Bug", + "formeLetter": "B", + "types": [ + "Bug" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusdark": { + "num": 493, + "species": "Arceus-Dark", + "baseSpecies": "Arceus", + "forme": "Dark", + "formeLetter": "D", + "types": [ + "Dark" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusdragon": { + "num": 493, + "species": "Arceus-Dragon", + "baseSpecies": "Arceus", + "forme": "Dragon", + "formeLetter": "D", + "types": [ + "Dragon" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceuselectric": { + "num": 493, + "species": "Arceus-Electric", + "baseSpecies": "Arceus", + "forme": "Electric", + "formeLetter": "E", + "types": [ + "Electric" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusfairy": { + "num": 493, + "species": "Arceus-Fairy", + "baseSpecies": "Arceus", + "forme": "Fairy", + "formeLetter": "F", + "types": [ + "Fairy" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusfighting": { + "num": 493, + "species": "Arceus-Fighting", + "baseSpecies": "Arceus", + "forme": "Fighting", + "formeLetter": "F", + "types": [ + "Fighting" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusfire": { + "num": 493, + "species": "Arceus-Fire", + "baseSpecies": "Arceus", + "forme": "Fire", + "formeLetter": "F", + "types": [ + "Fire" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusflying": { + "num": 493, + "species": "Arceus-Flying", + "baseSpecies": "Arceus", + "forme": "Flying", + "formeLetter": "F", + "types": [ + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusghost": { + "num": 493, + "species": "Arceus-Ghost", + "baseSpecies": "Arceus", + "forme": "Ghost", + "formeLetter": "G", + "types": [ + "Ghost" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusgrass": { + "num": 493, + "species": "Arceus-Grass", + "baseSpecies": "Arceus", + "forme": "Grass", + "formeLetter": "G", + "types": [ + "Grass" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusground": { + "num": 493, + "species": "Arceus-Ground", + "baseSpecies": "Arceus", + "forme": "Ground", + "formeLetter": "G", + "types": [ + "Ground" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusice": { + "num": 493, + "species": "Arceus-Ice", + "baseSpecies": "Arceus", + "forme": "Ice", + "formeLetter": "I", + "types": [ + "Ice" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceuspoison": { + "num": 493, + "species": "Arceus-Poison", + "baseSpecies": "Arceus", + "forme": "Poison", + "formeLetter": "P", + "types": [ + "Poison" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceuspsychic": { + "num": 493, + "species": "Arceus-Psychic", + "baseSpecies": "Arceus", + "forme": "Psychic", + "formeLetter": "P", + "types": [ + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceusrock": { + "num": 493, + "species": "Arceus-Rock", + "baseSpecies": "Arceus", + "forme": "Rock", + "formeLetter": "R", + "types": [ + "Rock" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceussteel": { + "num": 493, + "species": "Arceus-Steel", + "baseSpecies": "Arceus", + "forme": "Steel", + "formeLetter": "S", + "types": [ + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arceuswater": { + "num": 493, + "species": "Arceus-Water", + "baseSpecies": "Arceus", + "forme": "Water", + "formeLetter": "W", + "types": [ + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 120, + "atk": 120, + "def": 120, + "spa": 120, + "spd": 120, + "spe": 120 + }, + "abilities": { + "0": "Multitype" + }, + "heightm": 3.2, + "weightkg": 320, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "victini": { + "num": 494, + "species": "Victini", + "types": [ + "Psychic", + "Fire" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 100, + "def": 100, + "spa": 100, + "spd": 100, + "spe": 100 + }, + "abilities": { + "0": "Victory Star" + }, + "heightm": 0.4, + "weightkg": 4, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "snivy": { + "num": 495, + "species": "Snivy", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 45, + "atk": 45, + "def": 55, + "spa": 45, + "spd": 55, + "spe": 63 + }, + "abilities": { + "0": "Overgrow", + "H": "Contrary" + }, + "heightm": 0.6, + "weightkg": 8.1, + "color": "Green", + "evos": [ + "servine" + ], + "eggGroups": [ + "Field", + "Grass" + ] + }, + "servine": { + "num": 496, + "species": "Servine", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 60, + "atk": 60, + "def": 75, + "spa": 60, + "spd": 75, + "spe": 83 + }, + "abilities": { + "0": "Overgrow", + "H": "Contrary" + }, + "heightm": 0.8, + "weightkg": 16, + "color": "Green", + "prevo": "snivy", + "evos": [ + "serperior" + ], + "evoLevel": 17, + "eggGroups": [ + "Field", + "Grass" + ] + }, + "serperior": { + "num": 497, + "species": "Serperior", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 75, + "def": 95, + "spa": 75, + "spd": 95, + "spe": 113 + }, + "abilities": { + "0": "Overgrow", + "H": "Contrary" + }, + "heightm": 3.3, + "weightkg": 63, + "color": "Green", + "prevo": "servine", + "evoLevel": 36, + "eggGroups": [ + "Field", + "Grass" + ] + }, + "tepig": { + "num": 498, + "species": "Tepig", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 65, + "atk": 63, + "def": 45, + "spa": 45, + "spd": 45, + "spe": 45 + }, + "abilities": { + "0": "Blaze", + "H": "Thick Fat" + }, + "heightm": 0.5, + "weightkg": 9.9, + "color": "Red", + "evos": [ + "pignite" + ], + "eggGroups": [ + "Field" + ] + }, + "pignite": { + "num": 499, + "species": "Pignite", + "types": [ + "Fire", + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 90, + "atk": 93, + "def": 55, + "spa": 70, + "spd": 55, + "spe": 55 + }, + "abilities": { + "0": "Blaze", + "H": "Thick Fat" + }, + "heightm": 1, + "weightkg": 55.5, + "color": "Red", + "prevo": "tepig", + "evos": [ + "emboar" + ], + "evoLevel": 17, + "eggGroups": [ + "Field" + ] + }, + "emboar": { + "num": 500, + "species": "Emboar", + "types": [ + "Fire", + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 110, + "atk": 123, + "def": 65, + "spa": 100, + "spd": 65, + "spe": 65 + }, + "abilities": { + "0": "Blaze", + "H": "Reckless" + }, + "heightm": 1.6, + "weightkg": 150, + "color": "Red", + "prevo": "pignite", + "evoLevel": 36, + "eggGroups": [ + "Field" + ] + }, + "oshawott": { + "num": 501, + "species": "Oshawott", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 55, + "atk": 55, + "def": 45, + "spa": 63, + "spd": 45, + "spe": 45 + }, + "abilities": { + "0": "Torrent", + "H": "Shell Armor" + }, + "heightm": 0.5, + "weightkg": 5.9, + "color": "Blue", + "evos": [ + "dewott" + ], + "eggGroups": [ + "Field" + ] + }, + "dewott": { + "num": 502, + "species": "Dewott", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 75, + "def": 60, + "spa": 83, + "spd": 60, + "spe": 60 + }, + "abilities": { + "0": "Torrent", + "H": "Shell Armor" + }, + "heightm": 0.8, + "weightkg": 24.5, + "color": "Blue", + "prevo": "oshawott", + "evos": [ + "samurott" + ], + "evoLevel": 17, + "eggGroups": [ + "Field" + ] + }, + "samurott": { + "num": 503, + "species": "Samurott", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 95, + "atk": 100, + "def": 85, + "spa": 108, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Torrent", + "H": "Shell Armor" + }, + "heightm": 1.5, + "weightkg": 94.6, + "color": "Blue", + "prevo": "dewott", + "evoLevel": 36, + "eggGroups": [ + "Field" + ] + }, + "patrat": { + "num": 504, + "species": "Patrat", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 45, + "atk": 55, + "def": 39, + "spa": 35, + "spd": 39, + "spe": 42 + }, + "abilities": { + "0": "Run Away", + "1": "Keen Eye", + "H": "Analytic" + }, + "heightm": 0.5, + "weightkg": 11.6, + "color": "Brown", + "evos": [ + "watchog" + ], + "eggGroups": [ + "Field" + ] + }, + "watchog": { + "num": 505, + "species": "Watchog", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 60, + "atk": 85, + "def": 69, + "spa": 60, + "spd": 69, + "spe": 77 + }, + "abilities": { + "0": "Illuminate", + "1": "Keen Eye", + "H": "Analytic" + }, + "heightm": 1.1, + "weightkg": 27, + "color": "Brown", + "prevo": "patrat", + "evoLevel": 20, + "eggGroups": [ + "Field" + ] + }, + "lillipup": { + "num": 506, + "species": "Lillipup", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 45, + "atk": 60, + "def": 45, + "spa": 25, + "spd": 45, + "spe": 55 + }, + "abilities": { + "0": "Vital Spirit", + "1": "Pickup", + "H": "Run Away" + }, + "heightm": 0.4, + "weightkg": 4.1, + "color": "Brown", + "evos": [ + "herdier" + ], + "eggGroups": [ + "Field" + ] + }, + "herdier": { + "num": 507, + "species": "Herdier", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 65, + "atk": 80, + "def": 65, + "spa": 35, + "spd": 65, + "spe": 60 + }, + "abilities": { + "0": "Intimidate", + "1": "Sand Rush", + "H": "Scrappy" + }, + "heightm": 0.9, + "weightkg": 14.7, + "color": "Gray", + "prevo": "lillipup", + "evos": [ + "stoutland" + ], + "evoLevel": 16, + "eggGroups": [ + "Field" + ] + }, + "stoutland": { + "num": 508, + "species": "Stoutland", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 85, + "atk": 110, + "def": 90, + "spa": 45, + "spd": 90, + "spe": 80 + }, + "abilities": { + "0": "Intimidate", + "1": "Sand Rush", + "H": "Scrappy" + }, + "heightm": 1.2, + "weightkg": 61, + "color": "Gray", + "prevo": "herdier", + "evoLevel": 32, + "eggGroups": [ + "Field" + ] + }, + "purrloin": { + "num": 509, + "species": "Purrloin", + "types": [ + "Dark" + ], + "baseStats": { + "hp": 41, + "atk": 50, + "def": 37, + "spa": 50, + "spd": 37, + "spe": 66 + }, + "abilities": { + "0": "Limber", + "1": "Unburden", + "H": "Prankster" + }, + "heightm": 0.4, + "weightkg": 10.1, + "color": "Purple", + "evos": [ + "liepard" + ], + "eggGroups": [ + "Field" + ] + }, + "liepard": { + "num": 510, + "species": "Liepard", + "types": [ + "Dark" + ], + "baseStats": { + "hp": 64, + "atk": 88, + "def": 50, + "spa": 88, + "spd": 50, + "spe": 106 + }, + "abilities": { + "0": "Limber", + "1": "Unburden", + "H": "Prankster" + }, + "heightm": 1.1, + "weightkg": 37.5, + "color": "Purple", + "prevo": "purrloin", + "evoLevel": 20, + "eggGroups": [ + "Field" + ] + }, + "pansage": { + "num": 511, + "species": "Pansage", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 50, + "atk": 53, + "def": 48, + "spa": 53, + "spd": 48, + "spe": 64 + }, + "abilities": { + "0": "Gluttony", + "H": "Overgrow" + }, + "heightm": 0.6, + "weightkg": 10.5, + "color": "Green", + "evos": [ + "simisage" + ], + "eggGroups": [ + "Field" + ] + }, + "simisage": { + "num": 512, + "species": "Simisage", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 98, + "def": 63, + "spa": 98, + "spd": 63, + "spe": 101 + }, + "abilities": { + "0": "Gluttony", + "H": "Overgrow" + }, + "heightm": 1.1, + "weightkg": 30.5, + "color": "Green", + "prevo": "pansage", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "pansear": { + "num": 513, + "species": "Pansear", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 50, + "atk": 53, + "def": 48, + "spa": 53, + "spd": 48, + "spe": 64 + }, + "abilities": { + "0": "Gluttony", + "H": "Blaze" + }, + "heightm": 0.6, + "weightkg": 11, + "color": "Red", + "evos": [ + "simisear" + ], + "eggGroups": [ + "Field" + ] + }, + "simisear": { + "num": 514, + "species": "Simisear", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 98, + "def": 63, + "spa": 98, + "spd": 63, + "spe": 101 + }, + "abilities": { + "0": "Gluttony", + "H": "Blaze" + }, + "heightm": 1, + "weightkg": 28, + "color": "Red", + "prevo": "pansear", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "panpour": { + "num": 515, + "species": "Panpour", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 50, + "atk": 53, + "def": 48, + "spa": 53, + "spd": 48, + "spe": 64 + }, + "abilities": { + "0": "Gluttony", + "H": "Torrent" + }, + "heightm": 0.6, + "weightkg": 13.5, + "color": "Blue", + "evos": [ + "simipour" + ], + "eggGroups": [ + "Field" + ] + }, + "simipour": { + "num": 516, + "species": "Simipour", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 98, + "def": 63, + "spa": 98, + "spd": 63, + "spe": 101 + }, + "abilities": { + "0": "Gluttony", + "H": "Torrent" + }, + "heightm": 1, + "weightkg": 29, + "color": "Blue", + "prevo": "panpour", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "munna": { + "num": 517, + "species": "Munna", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 76, + "atk": 25, + "def": 45, + "spa": 67, + "spd": 55, + "spe": 24 + }, + "abilities": { + "0": "Forewarn", + "1": "Synchronize", + "H": "Telepathy" + }, + "heightm": 0.6, + "weightkg": 23.3, + "color": "Pink", + "evos": [ + "musharna" + ], + "eggGroups": [ + "Field" + ] + }, + "musharna": { + "num": 518, + "species": "Musharna", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 116, + "atk": 55, + "def": 85, + "spa": 107, + "spd": 95, + "spe": 29 + }, + "abilities": { + "0": "Forewarn", + "1": "Synchronize", + "H": "Telepathy" + }, + "heightm": 1.1, + "weightkg": 60.5, + "color": "Pink", + "prevo": "munna", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "pidove": { + "num": 519, + "species": "Pidove", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 50, + "atk": 55, + "def": 50, + "spa": 36, + "spd": 30, + "spe": 43 + }, + "abilities": { + "0": "Big Pecks", + "1": "Super Luck", + "H": "Rivalry" + }, + "heightm": 0.3, + "weightkg": 2.1, + "color": "Gray", + "evos": [ + "tranquill" + ], + "eggGroups": [ + "Flying" + ] + }, + "tranquill": { + "num": 520, + "species": "Tranquill", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 62, + "atk": 77, + "def": 62, + "spa": 50, + "spd": 42, + "spe": 65 + }, + "abilities": { + "0": "Big Pecks", + "1": "Super Luck", + "H": "Rivalry" + }, + "heightm": 0.6, + "weightkg": 15, + "color": "Gray", + "prevo": "pidove", + "evos": [ + "unfezant" + ], + "evoLevel": 21, + "eggGroups": [ + "Flying" + ] + }, + "unfezant": { + "num": 521, + "species": "Unfezant", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 80, + "atk": 115, + "def": 80, + "spa": 65, + "spd": 55, + "spe": 93 + }, + "abilities": { + "0": "Big Pecks", + "1": "Super Luck", + "H": "Rivalry" + }, + "heightm": 1.2, + "weightkg": 29, + "color": "Gray", + "prevo": "tranquill", + "evoLevel": 32, + "eggGroups": [ + "Flying" + ] + }, + "blitzle": { + "num": 522, + "species": "Blitzle", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 45, + "atk": 60, + "def": 32, + "spa": 50, + "spd": 32, + "spe": 76 + }, + "abilities": { + "0": "Lightning Rod", + "1": "Motor Drive", + "H": "Sap Sipper" + }, + "heightm": 0.8, + "weightkg": 29.8, + "color": "Black", + "evos": [ + "zebstrika" + ], + "eggGroups": [ + "Field" + ] + }, + "zebstrika": { + "num": 523, + "species": "Zebstrika", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 75, + "atk": 100, + "def": 63, + "spa": 80, + "spd": 63, + "spe": 116 + }, + "abilities": { + "0": "Lightning Rod", + "1": "Motor Drive", + "H": "Sap Sipper" + }, + "heightm": 1.6, + "weightkg": 79.5, + "color": "Black", + "prevo": "blitzle", + "evoLevel": 27, + "eggGroups": [ + "Field" + ] + }, + "roggenrola": { + "num": 524, + "species": "Roggenrola", + "types": [ + "Rock" + ], + "baseStats": { + "hp": 55, + "atk": 75, + "def": 85, + "spa": 25, + "spd": 25, + "spe": 15 + }, + "abilities": { + "0": "Sturdy", + "H": "Sand Force" + }, + "heightm": 0.4, + "weightkg": 18, + "color": "Blue", + "evos": [ + "boldore" + ], + "eggGroups": [ + "Mineral" + ] + }, + "boldore": { + "num": 525, + "species": "Boldore", + "types": [ + "Rock" + ], + "baseStats": { + "hp": 70, + "atk": 105, + "def": 105, + "spa": 50, + "spd": 40, + "spe": 20 + }, + "abilities": { + "0": "Sturdy", + "H": "Sand Force" + }, + "heightm": 0.9, + "weightkg": 102, + "color": "Blue", + "prevo": "roggenrola", + "evos": [ + "gigalith" + ], + "evoLevel": 25, + "eggGroups": [ + "Mineral" + ] + }, + "gigalith": { + "num": 526, + "species": "Gigalith", + "types": [ + "Rock" + ], + "baseStats": { + "hp": 85, + "atk": 135, + "def": 130, + "spa": 60, + "spd": 80, + "spe": 25 + }, + "abilities": { + "0": "Sturdy", + "H": "Sand Force" + }, + "heightm": 1.7, + "weightkg": 260, + "color": "Blue", + "prevo": "boldore", + "evoLevel": 25, + "eggGroups": [ + "Mineral" + ] + }, + "woobat": { + "num": 527, + "species": "Woobat", + "types": [ + "Psychic", + "Flying" + ], + "baseStats": { + "hp": 55, + "atk": 45, + "def": 43, + "spa": 55, + "spd": 43, + "spe": 72 + }, + "abilities": { + "0": "Unaware", + "1": "Klutz", + "H": "Simple" + }, + "heightm": 0.4, + "weightkg": 2.1, + "color": "Blue", + "evos": [ + "swoobat" + ], + "eggGroups": [ + "Flying", + "Field" + ] + }, + "swoobat": { + "num": 528, + "species": "Swoobat", + "types": [ + "Psychic", + "Flying" + ], + "baseStats": { + "hp": 67, + "atk": 57, + "def": 55, + "spa": 77, + "spd": 55, + "spe": 114 + }, + "abilities": { + "0": "Unaware", + "1": "Klutz", + "H": "Simple" + }, + "heightm": 0.9, + "weightkg": 10.5, + "color": "Blue", + "prevo": "woobat", + "evoLevel": 2, + "eggGroups": [ + "Flying", + "Field" + ] + }, + "drilbur": { + "num": 529, + "species": "Drilbur", + "types": [ + "Ground" + ], + "baseStats": { + "hp": 60, + "atk": 85, + "def": 40, + "spa": 30, + "spd": 45, + "spe": 68 + }, + "abilities": { + "0": "Sand Rush", + "1": "Sand Force", + "H": "Mold Breaker" + }, + "heightm": 0.3, + "weightkg": 8.5, + "color": "Gray", + "evos": [ + "excadrill" + ], + "eggGroups": [ + "Field" + ] + }, + "excadrill": { + "num": 530, + "species": "Excadrill", + "types": [ + "Ground", + "Steel" + ], + "baseStats": { + "hp": 110, + "atk": 135, + "def": 60, + "spa": 50, + "spd": 65, + "spe": 88 + }, + "abilities": { + "0": "Sand Rush", + "1": "Sand Force", + "H": "Mold Breaker" + }, + "heightm": 0.7, + "weightkg": 40.4, + "color": "Gray", + "prevo": "drilbur", + "evoLevel": 31, + "eggGroups": [ + "Field" + ] + }, + "audino": { + "num": 531, + "species": "Audino", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 103, + "atk": 60, + "def": 86, + "spa": 60, + "spd": 86, + "spe": 50 + }, + "abilities": { + "0": "Healer", + "1": "Regenerator", + "H": "Klutz" + }, + "heightm": 1.1, + "weightkg": 31, + "color": "Pink", + "eggGroups": [ + "Fairy" + ], + "otherFormes": [ + "audinomega" + ] + }, + "audinomega": { + "num": 531, + "species": "Audino-Mega", + "baseSpecies": "Audino", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Normal", + "Fairy" + ], + "baseStats": { + "hp": 103, + "atk": 60, + "def": 126, + "spa": 80, + "spd": 126, + "spe": 50 + }, + "abilities": { + "0": "Healer" + }, + "heightm": 1.5, + "weightkg": 32, + "color": "Pink", + "eggGroups": [ + "Fairy" + ] + }, + "timburr": { + "num": 532, + "species": "Timburr", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 75, + "atk": 80, + "def": 55, + "spa": 25, + "spd": 35, + "spe": 35 + }, + "abilities": { + "0": "Guts", + "1": "Sheer Force", + "H": "Iron Fist" + }, + "heightm": 0.6, + "weightkg": 12.5, + "color": "Gray", + "evos": [ + "gurdurr" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "gurdurr": { + "num": 533, + "species": "Gurdurr", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 85, + "atk": 105, + "def": 85, + "spa": 40, + "spd": 50, + "spe": 40 + }, + "abilities": { + "0": "Guts", + "1": "Sheer Force", + "H": "Iron Fist" + }, + "heightm": 1.2, + "weightkg": 40, + "color": "Gray", + "prevo": "timburr", + "evos": [ + "conkeldurr" + ], + "evoLevel": 25, + "eggGroups": [ + "Human-Like" + ] + }, + "conkeldurr": { + "num": 534, + "species": "Conkeldurr", + "types": [ + "Fighting" + ], + "genderRatio": { + "M": 0.75, + "F": 0.25 + }, + "baseStats": { + "hp": 105, + "atk": 140, + "def": 95, + "spa": 55, + "spd": 65, + "spe": 45 + }, + "abilities": { + "0": "Guts", + "1": "Sheer Force", + "H": "Iron Fist" + }, + "heightm": 1.4, + "weightkg": 87, + "color": "Brown", + "prevo": "gurdurr", + "evoLevel": 25, + "eggGroups": [ + "Human-Like" + ] + }, + "tympole": { + "num": 535, + "species": "Tympole", + "types": [ + "Water" + ], + "baseStats": { + "hp": 50, + "atk": 50, + "def": 40, + "spa": 50, + "spd": 40, + "spe": 64 + }, + "abilities": { + "0": "Swift Swim", + "1": "Hydration", + "H": "Water Absorb" + }, + "heightm": 0.5, + "weightkg": 4.5, + "color": "Blue", + "evos": [ + "palpitoad" + ], + "eggGroups": [ + "Water 1" + ] + }, + "palpitoad": { + "num": 536, + "species": "Palpitoad", + "types": [ + "Water", + "Ground" + ], + "baseStats": { + "hp": 75, + "atk": 65, + "def": 55, + "spa": 65, + "spd": 55, + "spe": 69 + }, + "abilities": { + "0": "Swift Swim", + "1": "Hydration", + "H": "Water Absorb" + }, + "heightm": 0.8, + "weightkg": 17, + "color": "Blue", + "prevo": "tympole", + "evos": [ + "seismitoad" + ], + "evoLevel": 25, + "eggGroups": [ + "Water 1" + ] + }, + "seismitoad": { + "num": 537, + "species": "Seismitoad", + "types": [ + "Water", + "Ground" + ], + "baseStats": { + "hp": 105, + "atk": 95, + "def": 75, + "spa": 85, + "spd": 75, + "spe": 74 + }, + "abilities": { + "0": "Swift Swim", + "1": "Poison Touch", + "H": "Water Absorb" + }, + "heightm": 1.5, + "weightkg": 62, + "color": "Blue", + "prevo": "palpitoad", + "evoLevel": 36, + "eggGroups": [ + "Water 1" + ] + }, + "throh": { + "num": 538, + "species": "Throh", + "types": [ + "Fighting" + ], + "gender": "M", + "baseStats": { + "hp": 120, + "atk": 100, + "def": 85, + "spa": 30, + "spd": 85, + "spe": 45 + }, + "abilities": { + "0": "Guts", + "1": "Inner Focus", + "H": "Mold Breaker" + }, + "heightm": 1.3, + "weightkg": 55.5, + "color": "Red", + "eggGroups": [ + "Human-Like" + ] + }, + "sawk": { + "num": 539, + "species": "Sawk", + "types": [ + "Fighting" + ], + "gender": "M", + "baseStats": { + "hp": 75, + "atk": 125, + "def": 75, + "spa": 30, + "spd": 75, + "spe": 85 + }, + "abilities": { + "0": "Sturdy", + "1": "Inner Focus", + "H": "Mold Breaker" + }, + "heightm": 1.4, + "weightkg": 51, + "color": "Blue", + "eggGroups": [ + "Human-Like" + ] + }, + "sewaddle": { + "num": 540, + "species": "Sewaddle", + "types": [ + "Bug", + "Grass" + ], + "baseStats": { + "hp": 45, + "atk": 53, + "def": 70, + "spa": 40, + "spd": 60, + "spe": 42 + }, + "abilities": { + "0": "Swarm", + "1": "Chlorophyll", + "H": "Overcoat" + }, + "heightm": 0.3, + "weightkg": 2.5, + "color": "Yellow", + "evos": [ + "swadloon" + ], + "eggGroups": [ + "Bug" + ] + }, + "swadloon": { + "num": 541, + "species": "Swadloon", + "types": [ + "Bug", + "Grass" + ], + "baseStats": { + "hp": 55, + "atk": 63, + "def": 90, + "spa": 50, + "spd": 80, + "spe": 42 + }, + "abilities": { + "0": "Leaf Guard", + "1": "Chlorophyll", + "H": "Overcoat" + }, + "heightm": 0.5, + "weightkg": 7.3, + "color": "Green", + "prevo": "sewaddle", + "evos": [ + "leavanny" + ], + "evoLevel": 20, + "eggGroups": [ + "Bug" + ] + }, + "leavanny": { + "num": 542, + "species": "Leavanny", + "types": [ + "Bug", + "Grass" + ], + "baseStats": { + "hp": 75, + "atk": 103, + "def": 80, + "spa": 70, + "spd": 80, + "spe": 92 + }, + "abilities": { + "0": "Swarm", + "1": "Chlorophyll", + "H": "Overcoat" + }, + "heightm": 1.2, + "weightkg": 20.5, + "color": "Yellow", + "prevo": "swadloon", + "evoLevel": 21, + "eggGroups": [ + "Bug" + ] + }, + "venipede": { + "num": 543, + "species": "Venipede", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 30, + "atk": 45, + "def": 59, + "spa": 30, + "spd": 39, + "spe": 57 + }, + "abilities": { + "0": "Poison Point", + "1": "Swarm", + "H": "Speed Boost" + }, + "heightm": 0.4, + "weightkg": 5.3, + "color": "Red", + "evos": [ + "whirlipede" + ], + "eggGroups": [ + "Bug" + ] + }, + "whirlipede": { + "num": 544, + "species": "Whirlipede", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 40, + "atk": 55, + "def": 99, + "spa": 40, + "spd": 79, + "spe": 47 + }, + "abilities": { + "0": "Poison Point", + "1": "Swarm", + "H": "Speed Boost" + }, + "heightm": 1.2, + "weightkg": 58.5, + "color": "Gray", + "prevo": "venipede", + "evos": [ + "scolipede" + ], + "evoLevel": 22, + "eggGroups": [ + "Bug" + ] + }, + "scolipede": { + "num": 545, + "species": "Scolipede", + "types": [ + "Bug", + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 100, + "def": 89, + "spa": 55, + "spd": 69, + "spe": 112 + }, + "abilities": { + "0": "Poison Point", + "1": "Swarm", + "H": "Speed Boost" + }, + "heightm": 2.5, + "weightkg": 200.5, + "color": "Red", + "prevo": "whirlipede", + "evoLevel": 30, + "eggGroups": [ + "Bug" + ] + }, + "cottonee": { + "num": 546, + "species": "Cottonee", + "types": [ + "Grass", + "Fairy" + ], + "baseStats": { + "hp": 40, + "atk": 27, + "def": 60, + "spa": 37, + "spd": 50, + "spe": 66 + }, + "abilities": { + "0": "Prankster", + "1": "Infiltrator", + "H": "Chlorophyll" + }, + "heightm": 0.3, + "weightkg": 0.6, + "color": "Green", + "evos": [ + "whimsicott" + ], + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "whimsicott": { + "num": 547, + "species": "Whimsicott", + "types": [ + "Grass", + "Fairy" + ], + "baseStats": { + "hp": 60, + "atk": 67, + "def": 85, + "spa": 77, + "spd": 75, + "spe": 116 + }, + "abilities": { + "0": "Prankster", + "1": "Infiltrator", + "H": "Chlorophyll" + }, + "heightm": 0.7, + "weightkg": 6.6, + "color": "Green", + "prevo": "cottonee", + "evoLevel": 1, + "eggGroups": [ + "Fairy", + "Grass" + ] + }, + "petilil": { + "num": 548, + "species": "Petilil", + "types": [ + "Grass" + ], + "gender": "F", + "baseStats": { + "hp": 45, + "atk": 35, + "def": 50, + "spa": 70, + "spd": 50, + "spe": 30 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Own Tempo", + "H": "Leaf Guard" + }, + "heightm": 0.5, + "weightkg": 6.6, + "color": "Green", + "evos": [ + "lilligant" + ], + "eggGroups": [ + "Grass" + ] + }, + "lilligant": { + "num": 549, + "species": "Lilligant", + "types": [ + "Grass" + ], + "gender": "F", + "baseStats": { + "hp": 70, + "atk": 60, + "def": 75, + "spa": 110, + "spd": 75, + "spe": 90 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Own Tempo", + "H": "Leaf Guard" + }, + "heightm": 1.1, + "weightkg": 16.3, + "color": "Green", + "prevo": "petilil", + "evoLevel": 1, + "eggGroups": [ + "Grass" + ] + }, + "basculin": { + "num": 550, + "species": "Basculin", + "baseForme": "Red-Striped", + "types": [ + "Water" + ], + "baseStats": { + "hp": 70, + "atk": 92, + "def": 65, + "spa": 80, + "spd": 55, + "spe": 98 + }, + "abilities": { + "0": "Reckless", + "1": "Adaptability", + "H": "Mold Breaker" + }, + "heightm": 1, + "weightkg": 18, + "color": "Green", + "eggGroups": [ + "Water 2" + ], + "otherFormes": [ + "basculinbluestriped" + ] + }, + "basculinbluestriped": { + "num": 550, + "species": "Basculin-Blue-Striped", + "baseSpecies": "Basculin", + "forme": "Blue-Striped", + "formeLetter": "B", + "types": [ + "Water" + ], + "baseStats": { + "hp": 70, + "atk": 92, + "def": 65, + "spa": 80, + "spd": 55, + "spe": 98 + }, + "abilities": { + "0": "Rock Head", + "1": "Adaptability", + "H": "Mold Breaker" + }, + "heightm": 1, + "weightkg": 18, + "color": "Green", + "eggGroups": [ + "Water 2" + ] + }, + "sandile": { + "num": 551, + "species": "Sandile", + "types": [ + "Ground", + "Dark" + ], + "baseStats": { + "hp": 50, + "atk": 72, + "def": 35, + "spa": 35, + "spd": 35, + "spe": 65 + }, + "abilities": { + "0": "Intimidate", + "1": "Moxie", + "H": "Anger Point" + }, + "heightm": 0.7, + "weightkg": 15.2, + "color": "Brown", + "evos": [ + "krokorok" + ], + "eggGroups": [ + "Field" + ] + }, + "krokorok": { + "num": 552, + "species": "Krokorok", + "types": [ + "Ground", + "Dark" + ], + "baseStats": { + "hp": 60, + "atk": 82, + "def": 45, + "spa": 45, + "spd": 45, + "spe": 74 + }, + "abilities": { + "0": "Intimidate", + "1": "Moxie", + "H": "Anger Point" + }, + "heightm": 1, + "weightkg": 33.4, + "color": "Brown", + "prevo": "sandile", + "evos": [ + "krookodile" + ], + "evoLevel": 29, + "eggGroups": [ + "Field" + ] + }, + "krookodile": { + "num": 553, + "species": "Krookodile", + "types": [ + "Ground", + "Dark" + ], + "baseStats": { + "hp": 95, + "atk": 117, + "def": 80, + "spa": 65, + "spd": 70, + "spe": 92 + }, + "abilities": { + "0": "Intimidate", + "1": "Moxie", + "H": "Anger Point" + }, + "heightm": 1.5, + "weightkg": 96.3, + "color": "Red", + "prevo": "krokorok", + "evoLevel": 40, + "eggGroups": [ + "Field" + ] + }, + "darumaka": { + "num": 554, + "species": "Darumaka", + "types": [ + "Fire" + ], + "baseStats": { + "hp": 70, + "atk": 90, + "def": 45, + "spa": 15, + "spd": 45, + "spe": 50 + }, + "abilities": { + "0": "Hustle", + "H": "Inner Focus" + }, + "heightm": 0.6, + "weightkg": 37.5, + "color": "Red", + "evos": [ + "darmanitan" + ], + "eggGroups": [ + "Field" + ] + }, + "darmanitan": { + "num": 555, + "species": "Darmanitan", + "baseForme": "Standard", + "types": [ + "Fire" + ], + "baseStats": { + "hp": 105, + "atk": 140, + "def": 55, + "spa": 30, + "spd": 55, + "spe": 95 + }, + "abilities": { + "0": "Sheer Force", + "H": "Zen Mode" + }, + "heightm": 1.3, + "weightkg": 92.9, + "color": "Red", + "prevo": "darumaka", + "evoLevel": 35, + "eggGroups": [ + "Field" + ], + "otherFormes": [ + "darmanitanzen" + ] + }, + "darmanitanzen": { + "num": 555, + "species": "Darmanitan-Zen", + "baseSpecies": "Darmanitan", + "forme": "Zen", + "formeLetter": "Z", + "types": [ + "Fire", + "Psychic" + ], + "baseStats": { + "hp": 105, + "atk": 30, + "def": 105, + "spa": 140, + "spd": 105, + "spe": 55 + }, + "abilities": { + "0": "Zen Mode" + }, + "heightm": 1.3, + "weightkg": 92.9, + "color": "Red", + "prevo": "darumaka", + "evoLevel": 35, + "eggGroups": [ + "Field" + ] + }, + "maractus": { + "num": 556, + "species": "Maractus", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 75, + "atk": 86, + "def": 67, + "spa": 106, + "spd": 67, + "spe": 60 + }, + "abilities": { + "0": "Water Absorb", + "1": "Chlorophyll", + "H": "Storm Drain" + }, + "heightm": 1, + "weightkg": 28, + "color": "Green", + "eggGroups": [ + "Grass" + ] + }, + "dwebble": { + "num": 557, + "species": "Dwebble", + "types": [ + "Bug", + "Rock" + ], + "baseStats": { + "hp": 50, + "atk": 65, + "def": 85, + "spa": 35, + "spd": 35, + "spe": 55 + }, + "abilities": { + "0": "Sturdy", + "1": "Shell Armor", + "H": "Weak Armor" + }, + "heightm": 0.3, + "weightkg": 14.5, + "color": "Red", + "evos": [ + "crustle" + ], + "eggGroups": [ + "Bug", + "Mineral" + ] + }, + "crustle": { + "num": 558, + "species": "Crustle", + "types": [ + "Bug", + "Rock" + ], + "baseStats": { + "hp": 70, + "atk": 95, + "def": 125, + "spa": 65, + "spd": 75, + "spe": 45 + }, + "abilities": { + "0": "Sturdy", + "1": "Shell Armor", + "H": "Weak Armor" + }, + "heightm": 1.4, + "weightkg": 200, + "color": "Red", + "prevo": "dwebble", + "evoLevel": 34, + "eggGroups": [ + "Bug", + "Mineral" + ] + }, + "scraggy": { + "num": 559, + "species": "Scraggy", + "types": [ + "Dark", + "Fighting" + ], + "baseStats": { + "hp": 50, + "atk": 75, + "def": 70, + "spa": 35, + "spd": 70, + "spe": 48 + }, + "abilities": { + "0": "Shed Skin", + "1": "Moxie", + "H": "Intimidate" + }, + "heightm": 0.6, + "weightkg": 11.8, + "color": "Yellow", + "evos": [ + "scrafty" + ], + "eggGroups": [ + "Field", + "Dragon" + ] + }, + "scrafty": { + "num": 560, + "species": "Scrafty", + "types": [ + "Dark", + "Fighting" + ], + "baseStats": { + "hp": 65, + "atk": 90, + "def": 115, + "spa": 45, + "spd": 115, + "spe": 58 + }, + "abilities": { + "0": "Shed Skin", + "1": "Moxie", + "H": "Intimidate" + }, + "heightm": 1.1, + "weightkg": 30, + "color": "Red", + "prevo": "scraggy", + "evoLevel": 39, + "eggGroups": [ + "Field", + "Dragon" + ] + }, + "sigilyph": { + "num": 561, + "species": "Sigilyph", + "types": [ + "Psychic", + "Flying" + ], + "baseStats": { + "hp": 72, + "atk": 58, + "def": 80, + "spa": 103, + "spd": 80, + "spe": 97 + }, + "abilities": { + "0": "Wonder Skin", + "1": "Magic Guard", + "H": "Tinted Lens" + }, + "heightm": 1.4, + "weightkg": 14, + "color": "Black", + "eggGroups": [ + "Flying" + ] + }, + "yamask": { + "num": 562, + "species": "Yamask", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 38, + "atk": 30, + "def": 85, + "spa": 55, + "spd": 65, + "spe": 30 + }, + "abilities": { + "0": "Mummy" + }, + "heightm": 0.5, + "weightkg": 1.5, + "color": "Black", + "evos": [ + "cofagrigus" + ], + "eggGroups": [ + "Mineral", + "Amorphous" + ] + }, + "cofagrigus": { + "num": 563, + "species": "Cofagrigus", + "types": [ + "Ghost" + ], + "baseStats": { + "hp": 58, + "atk": 50, + "def": 145, + "spa": 95, + "spd": 105, + "spe": 30 + }, + "abilities": { + "0": "Mummy" + }, + "heightm": 1.7, + "weightkg": 76.5, + "color": "Yellow", + "prevo": "yamask", + "evoLevel": 34, + "eggGroups": [ + "Mineral", + "Amorphous" + ] + }, + "tirtouga": { + "num": 564, + "species": "Tirtouga", + "types": [ + "Water", + "Rock" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 54, + "atk": 78, + "def": 103, + "spa": 53, + "spd": 45, + "spe": 22 + }, + "abilities": { + "0": "Solid Rock", + "1": "Sturdy", + "H": "Swift Swim" + }, + "heightm": 0.7, + "weightkg": 16.5, + "color": "Blue", + "evos": [ + "carracosta" + ], + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "carracosta": { + "num": 565, + "species": "Carracosta", + "types": [ + "Water", + "Rock" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 74, + "atk": 108, + "def": 133, + "spa": 83, + "spd": 65, + "spe": 32 + }, + "abilities": { + "0": "Solid Rock", + "1": "Sturdy", + "H": "Swift Swim" + }, + "heightm": 1.2, + "weightkg": 81, + "color": "Blue", + "prevo": "tirtouga", + "evoLevel": 37, + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "archen": { + "num": 566, + "species": "Archen", + "types": [ + "Rock", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 55, + "atk": 112, + "def": 45, + "spa": 74, + "spd": 45, + "spe": 70 + }, + "abilities": { + "0": "Defeatist" + }, + "heightm": 0.5, + "weightkg": 9.5, + "color": "Yellow", + "evos": [ + "archeops" + ], + "eggGroups": [ + "Flying", + "Water 3" + ] + }, + "archeops": { + "num": 567, + "species": "Archeops", + "types": [ + "Rock", + "Flying" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 140, + "def": 65, + "spa": 112, + "spd": 65, + "spe": 110 + }, + "abilities": { + "0": "Defeatist" + }, + "heightm": 1.4, + "weightkg": 32, + "color": "Yellow", + "prevo": "archen", + "evoLevel": 37, + "eggGroups": [ + "Flying", + "Water 3" + ] + }, + "trubbish": { + "num": 568, + "species": "Trubbish", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 50, + "atk": 50, + "def": 62, + "spa": 40, + "spd": 62, + "spe": 65 + }, + "abilities": { + "0": "Stench", + "1": "Sticky Hold", + "H": "Aftermath" + }, + "heightm": 0.6, + "weightkg": 31, + "color": "Green", + "evos": [ + "garbodor" + ], + "eggGroups": [ + "Mineral" + ] + }, + "garbodor": { + "num": 569, + "species": "Garbodor", + "types": [ + "Poison" + ], + "baseStats": { + "hp": 80, + "atk": 95, + "def": 82, + "spa": 60, + "spd": 82, + "spe": 75 + }, + "abilities": { + "0": "Stench", + "1": "Weak Armor", + "H": "Aftermath" + }, + "heightm": 1.9, + "weightkg": 107.3, + "color": "Green", + "prevo": "trubbish", + "evoLevel": 36, + "eggGroups": [ + "Mineral" + ] + }, + "zorua": { + "num": 570, + "species": "Zorua", + "types": [ + "Dark" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 40, + "atk": 65, + "def": 40, + "spa": 80, + "spd": 40, + "spe": 65 + }, + "abilities": { + "0": "Illusion" + }, + "heightm": 0.7, + "weightkg": 12.5, + "color": "Gray", + "evos": [ + "zoroark" + ], + "eggGroups": [ + "Field" + ] + }, + "zoroark": { + "num": 571, + "species": "Zoroark", + "types": [ + "Dark" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 60, + "atk": 105, + "def": 60, + "spa": 120, + "spd": 60, + "spe": 105 + }, + "abilities": { + "0": "Illusion" + }, + "heightm": 1.6, + "weightkg": 81.1, + "color": "Gray", + "prevo": "zorua", + "evoLevel": 30, + "eggGroups": [ + "Field" + ] + }, + "minccino": { + "num": 572, + "species": "Minccino", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 55, + "atk": 50, + "def": 40, + "spa": 40, + "spd": 40, + "spe": 75 + }, + "abilities": { + "0": "Cute Charm", + "1": "Technician", + "H": "Skill Link" + }, + "heightm": 0.4, + "weightkg": 5.8, + "color": "Gray", + "evos": [ + "cinccino" + ], + "eggGroups": [ + "Field" + ] + }, + "cinccino": { + "num": 573, + "species": "Cinccino", + "types": [ + "Normal" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 75, + "atk": 95, + "def": 60, + "spa": 65, + "spd": 60, + "spe": 115 + }, + "abilities": { + "0": "Cute Charm", + "1": "Technician", + "H": "Skill Link" + }, + "heightm": 0.5, + "weightkg": 7.5, + "color": "Gray", + "prevo": "minccino", + "evoLevel": 1, + "eggGroups": [ + "Field" + ] + }, + "gothita": { + "num": 574, + "species": "Gothita", + "types": [ + "Psychic" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 45, + "atk": 30, + "def": 50, + "spa": 55, + "spd": 65, + "spe": 45 + }, + "abilities": { + "0": "Frisk", + "1": "Competitive", + "H": "Shadow Tag" + }, + "heightm": 0.4, + "weightkg": 5.8, + "color": "Purple", + "evos": [ + "gothorita" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "gothorita": { + "num": 575, + "species": "Gothorita", + "types": [ + "Psychic" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 60, + "atk": 45, + "def": 70, + "spa": 75, + "spd": 85, + "spe": 55 + }, + "abilities": { + "0": "Frisk", + "1": "Competitive", + "H": "Shadow Tag" + }, + "heightm": 0.7, + "weightkg": 18, + "color": "Purple", + "prevo": "gothita", + "evos": [ + "gothitelle" + ], + "evoLevel": 32, + "eggGroups": [ + "Human-Like" + ] + }, + "gothitelle": { + "num": 576, + "species": "Gothitelle", + "types": [ + "Psychic" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 70, + "atk": 55, + "def": 95, + "spa": 95, + "spd": 110, + "spe": 65 + }, + "abilities": { + "0": "Frisk", + "1": "Competitive", + "H": "Shadow Tag" + }, + "heightm": 1.5, + "weightkg": 44, + "color": "Purple", + "prevo": "gothorita", + "evoLevel": 41, + "eggGroups": [ + "Human-Like" + ] + }, + "solosis": { + "num": 577, + "species": "Solosis", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 45, + "atk": 30, + "def": 40, + "spa": 105, + "spd": 50, + "spe": 20 + }, + "abilities": { + "0": "Overcoat", + "1": "Magic Guard", + "H": "Regenerator" + }, + "heightm": 0.3, + "weightkg": 1, + "color": "Green", + "evos": [ + "duosion" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "duosion": { + "num": 578, + "species": "Duosion", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 65, + "atk": 40, + "def": 50, + "spa": 125, + "spd": 60, + "spe": 30 + }, + "abilities": { + "0": "Overcoat", + "1": "Magic Guard", + "H": "Regenerator" + }, + "heightm": 0.6, + "weightkg": 8, + "color": "Green", + "prevo": "solosis", + "evos": [ + "reuniclus" + ], + "evoLevel": 32, + "eggGroups": [ + "Amorphous" + ] + }, + "reuniclus": { + "num": 579, + "species": "Reuniclus", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 110, + "atk": 65, + "def": 75, + "spa": 125, + "spd": 85, + "spe": 30 + }, + "abilities": { + "0": "Overcoat", + "1": "Magic Guard", + "H": "Regenerator" + }, + "heightm": 1, + "weightkg": 20.1, + "color": "Green", + "prevo": "duosion", + "evoLevel": 41, + "eggGroups": [ + "Amorphous" + ] + }, + "ducklett": { + "num": 580, + "species": "Ducklett", + "types": [ + "Water", + "Flying" + ], + "baseStats": { + "hp": 62, + "atk": 44, + "def": 50, + "spa": 44, + "spd": 50, + "spe": 55 + }, + "abilities": { + "0": "Keen Eye", + "1": "Big Pecks", + "H": "Hydration" + }, + "heightm": 0.5, + "weightkg": 5.5, + "color": "Blue", + "evos": [ + "swanna" + ], + "eggGroups": [ + "Water 1", + "Flying" + ] + }, + "swanna": { + "num": 581, + "species": "Swanna", + "types": [ + "Water", + "Flying" + ], + "baseStats": { + "hp": 75, + "atk": 87, + "def": 63, + "spa": 87, + "spd": 63, + "spe": 98 + }, + "abilities": { + "0": "Keen Eye", + "1": "Big Pecks", + "H": "Hydration" + }, + "heightm": 1.3, + "weightkg": 24.2, + "color": "White", + "prevo": "ducklett", + "evoLevel": 35, + "eggGroups": [ + "Water 1", + "Flying" + ] + }, + "vanillite": { + "num": 582, + "species": "Vanillite", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 36, + "atk": 50, + "def": 50, + "spa": 65, + "spd": 60, + "spe": 44 + }, + "abilities": { + "0": "Ice Body", + "H": "Weak Armor" + }, + "heightm": 0.4, + "weightkg": 5.7, + "color": "White", + "evos": [ + "vanillish" + ], + "eggGroups": [ + "Mineral" + ] + }, + "vanillish": { + "num": 583, + "species": "Vanillish", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 51, + "atk": 65, + "def": 65, + "spa": 80, + "spd": 75, + "spe": 59 + }, + "abilities": { + "0": "Ice Body", + "H": "Weak Armor" + }, + "heightm": 1.1, + "weightkg": 41, + "color": "White", + "prevo": "vanillite", + "evos": [ + "vanilluxe" + ], + "evoLevel": 35, + "eggGroups": [ + "Mineral" + ] + }, + "vanilluxe": { + "num": 584, + "species": "Vanilluxe", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 71, + "atk": 95, + "def": 85, + "spa": 110, + "spd": 95, + "spe": 79 + }, + "abilities": { + "0": "Ice Body", + "H": "Weak Armor" + }, + "heightm": 1.3, + "weightkg": 57.5, + "color": "White", + "prevo": "vanillish", + "evoLevel": 47, + "eggGroups": [ + "Mineral" + ] + }, + "deerling": { + "num": 585, + "species": "Deerling", + "baseForme": "Spring", + "types": [ + "Normal", + "Grass" + ], + "baseStats": { + "hp": 60, + "atk": 60, + "def": 50, + "spa": 40, + "spd": 50, + "spe": 75 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Sap Sipper", + "H": "Serene Grace" + }, + "heightm": 0.6, + "weightkg": 19.5, + "color": "Yellow", + "evos": [ + "sawsbuck" + ], + "eggGroups": [ + "Field" + ], + "otherForms": [ + "deerlingsummer", + "deerlingautumn", + "deerlingwinter" + ] + }, + "sawsbuck": { + "num": 586, + "species": "Sawsbuck", + "baseForme": "Spring", + "types": [ + "Normal", + "Grass" + ], + "baseStats": { + "hp": 80, + "atk": 100, + "def": 70, + "spa": 60, + "spd": 70, + "spe": 95 + }, + "abilities": { + "0": "Chlorophyll", + "1": "Sap Sipper", + "H": "Serene Grace" + }, + "heightm": 1.9, + "weightkg": 92.5, + "color": "Brown", + "prevo": "deerling", + "evoLevel": 34, + "eggGroups": [ + "Field" + ], + "otherForms": [ + "sawsbucksummer", + "sawsbuckautumn", + "sawsbuckwinter" + ] + }, + "emolga": { + "num": 587, + "species": "Emolga", + "types": [ + "Electric", + "Flying" + ], + "baseStats": { + "hp": 55, + "atk": 75, + "def": 60, + "spa": 75, + "spd": 60, + "spe": 103 + }, + "abilities": { + "0": "Static", + "H": "Motor Drive" + }, + "heightm": 0.4, + "weightkg": 5, + "color": "White", + "eggGroups": [ + "Field" + ] + }, + "karrablast": { + "num": 588, + "species": "Karrablast", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 50, + "atk": 75, + "def": 45, + "spa": 40, + "spd": 45, + "spe": 60 + }, + "abilities": { + "0": "Swarm", + "1": "Shed Skin", + "H": "No Guard" + }, + "heightm": 0.5, + "weightkg": 5.9, + "color": "Blue", + "evos": [ + "escavalier" + ], + "eggGroups": [ + "Bug" + ] + }, + "escavalier": { + "num": 589, + "species": "Escavalier", + "types": [ + "Bug", + "Steel" + ], + "baseStats": { + "hp": 70, + "atk": 135, + "def": 105, + "spa": 60, + "spd": 105, + "spe": 20 + }, + "abilities": { + "0": "Swarm", + "1": "Shell Armor", + "H": "Overcoat" + }, + "heightm": 1, + "weightkg": 33, + "color": "Gray", + "prevo": "karrablast", + "evoLevel": 1, + "eggGroups": [ + "Bug" + ] + }, + "foongus": { + "num": 590, + "species": "Foongus", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 69, + "atk": 55, + "def": 45, + "spa": 55, + "spd": 55, + "spe": 15 + }, + "abilities": { + "0": "Effect Spore", + "H": "Regenerator" + }, + "heightm": 0.2, + "weightkg": 1, + "color": "White", + "evos": [ + "amoonguss" + ], + "eggGroups": [ + "Grass" + ] + }, + "amoonguss": { + "num": 591, + "species": "Amoonguss", + "types": [ + "Grass", + "Poison" + ], + "baseStats": { + "hp": 114, + "atk": 85, + "def": 70, + "spa": 85, + "spd": 80, + "spe": 30 + }, + "abilities": { + "0": "Effect Spore", + "H": "Regenerator" + }, + "heightm": 0.6, + "weightkg": 10.5, + "color": "White", + "prevo": "foongus", + "evoLevel": 39, + "eggGroups": [ + "Grass" + ] + }, + "frillish": { + "num": 592, + "species": "Frillish", + "types": [ + "Water", + "Ghost" + ], + "baseStats": { + "hp": 55, + "atk": 40, + "def": 50, + "spa": 65, + "spd": 85, + "spe": 40 + }, + "abilities": { + "0": "Water Absorb", + "1": "Cursed Body", + "H": "Damp" + }, + "heightm": 1.2, + "weightkg": 33, + "color": "White", + "evos": [ + "jellicent" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "jellicent": { + "num": 593, + "species": "Jellicent", + "types": [ + "Water", + "Ghost" + ], + "baseStats": { + "hp": 100, + "atk": 60, + "def": 70, + "spa": 85, + "spd": 105, + "spe": 60 + }, + "abilities": { + "0": "Water Absorb", + "1": "Cursed Body", + "H": "Damp" + }, + "heightm": 2.2, + "weightkg": 135, + "color": "White", + "prevo": "frillish", + "evoLevel": 40, + "eggGroups": [ + "Amorphous" + ] + }, + "alomomola": { + "num": 594, + "species": "Alomomola", + "types": [ + "Water" + ], + "baseStats": { + "hp": 165, + "atk": 75, + "def": 80, + "spa": 40, + "spd": 45, + "spe": 65 + }, + "abilities": { + "0": "Healer", + "1": "Hydration", + "H": "Regenerator" + }, + "heightm": 1.2, + "weightkg": 31.6, + "color": "Pink", + "eggGroups": [ + "Water 1", + "Water 2" + ] + }, + "joltik": { + "num": 595, + "species": "Joltik", + "types": [ + "Bug", + "Electric" + ], + "baseStats": { + "hp": 50, + "atk": 47, + "def": 50, + "spa": 57, + "spd": 50, + "spe": 65 + }, + "abilities": { + "0": "Compound Eyes", + "1": "Unnerve", + "H": "Swarm" + }, + "heightm": 0.1, + "weightkg": 0.6, + "color": "Yellow", + "evos": [ + "galvantula" + ], + "eggGroups": [ + "Bug" + ] + }, + "galvantula": { + "num": 596, + "species": "Galvantula", + "types": [ + "Bug", + "Electric" + ], + "baseStats": { + "hp": 70, + "atk": 77, + "def": 60, + "spa": 97, + "spd": 60, + "spe": 108 + }, + "abilities": { + "0": "Compound Eyes", + "1": "Unnerve", + "H": "Swarm" + }, + "heightm": 0.8, + "weightkg": 14.3, + "color": "Yellow", + "prevo": "joltik", + "evoLevel": 36, + "eggGroups": [ + "Bug" + ] + }, + "ferroseed": { + "num": 597, + "species": "Ferroseed", + "types": [ + "Grass", + "Steel" + ], + "baseStats": { + "hp": 44, + "atk": 50, + "def": 91, + "spa": 24, + "spd": 86, + "spe": 10 + }, + "abilities": { + "0": "Iron Barbs" + }, + "heightm": 0.6, + "weightkg": 18.8, + "color": "Gray", + "evos": [ + "ferrothorn" + ], + "eggGroups": [ + "Grass", + "Mineral" + ] + }, + "ferrothorn": { + "num": 598, + "species": "Ferrothorn", + "types": [ + "Grass", + "Steel" + ], + "baseStats": { + "hp": 74, + "atk": 94, + "def": 131, + "spa": 54, + "spd": 116, + "spe": 20 + }, + "abilities": { + "0": "Iron Barbs", + "H": "Anticipation" + }, + "heightm": 1, + "weightkg": 110, + "color": "Gray", + "prevo": "ferroseed", + "evoLevel": 40, + "eggGroups": [ + "Grass", + "Mineral" + ] + }, + "klink": { + "num": 599, + "species": "Klink", + "types": [ + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 40, + "atk": 55, + "def": 70, + "spa": 45, + "spd": 60, + "spe": 30 + }, + "abilities": { + "0": "Plus", + "1": "Minus", + "H": "Clear Body" + }, + "heightm": 0.3, + "weightkg": 21, + "color": "Gray", + "evos": [ + "klang" + ], + "eggGroups": [ + "Mineral" + ] + }, + "klang": { + "num": 600, + "species": "Klang", + "types": [ + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 60, + "atk": 80, + "def": 95, + "spa": 70, + "spd": 85, + "spe": 50 + }, + "abilities": { + "0": "Plus", + "1": "Minus", + "H": "Clear Body" + }, + "heightm": 0.6, + "weightkg": 51, + "color": "Gray", + "prevo": "klink", + "evos": [ + "klinklang" + ], + "evoLevel": 38, + "eggGroups": [ + "Mineral" + ] + }, + "klinklang": { + "num": 601, + "species": "Klinklang", + "types": [ + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 60, + "atk": 100, + "def": 115, + "spa": 70, + "spd": 85, + "spe": 90 + }, + "abilities": { + "0": "Plus", + "1": "Minus", + "H": "Clear Body" + }, + "heightm": 0.6, + "weightkg": 81, + "color": "Gray", + "prevo": "klang", + "evoLevel": 49, + "eggGroups": [ + "Mineral" + ] + }, + "tynamo": { + "num": 602, + "species": "Tynamo", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 35, + "atk": 55, + "def": 40, + "spa": 45, + "spd": 40, + "spe": 60 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 0.2, + "weightkg": 0.3, + "color": "White", + "evos": [ + "eelektrik" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "eelektrik": { + "num": 603, + "species": "Eelektrik", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 65, + "atk": 85, + "def": 70, + "spa": 75, + "spd": 70, + "spe": 40 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.2, + "weightkg": 22, + "color": "Blue", + "prevo": "tynamo", + "evos": [ + "eelektross" + ], + "evoLevel": 39, + "eggGroups": [ + "Amorphous" + ] + }, + "eelektross": { + "num": 604, + "species": "Eelektross", + "types": [ + "Electric" + ], + "baseStats": { + "hp": 85, + "atk": 115, + "def": 80, + "spa": 105, + "spd": 80, + "spe": 50 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 2.1, + "weightkg": 80.5, + "color": "Blue", + "prevo": "eelektrik", + "evoLevel": 39, + "eggGroups": [ + "Amorphous" + ] + }, + "elgyem": { + "num": 605, + "species": "Elgyem", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 55, + "atk": 55, + "def": 55, + "spa": 85, + "spd": 55, + "spe": 30 + }, + "abilities": { + "0": "Telepathy", + "1": "Synchronize", + "H": "Analytic" + }, + "heightm": 0.5, + "weightkg": 9, + "color": "Blue", + "evos": [ + "beheeyem" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "beheeyem": { + "num": 606, + "species": "Beheeyem", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 75, + "atk": 75, + "def": 75, + "spa": 125, + "spd": 95, + "spe": 40 + }, + "abilities": { + "0": "Telepathy", + "1": "Synchronize", + "H": "Analytic" + }, + "heightm": 1, + "weightkg": 34.5, + "color": "Brown", + "prevo": "elgyem", + "evoLevel": 42, + "eggGroups": [ + "Human-Like" + ] + }, + "litwick": { + "num": 607, + "species": "Litwick", + "types": [ + "Ghost", + "Fire" + ], + "baseStats": { + "hp": 50, + "atk": 30, + "def": 55, + "spa": 65, + "spd": 55, + "spe": 20 + }, + "abilities": { + "0": "Flash Fire", + "1": "Flame Body", + "H": "Infiltrator" + }, + "heightm": 0.3, + "weightkg": 3.1, + "color": "White", + "evos": [ + "lampent" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "lampent": { + "num": 608, + "species": "Lampent", + "types": [ + "Ghost", + "Fire" + ], + "baseStats": { + "hp": 60, + "atk": 40, + "def": 60, + "spa": 95, + "spd": 60, + "spe": 55 + }, + "abilities": { + "0": "Flash Fire", + "1": "Flame Body", + "H": "Infiltrator" + }, + "heightm": 0.6, + "weightkg": 13, + "color": "Black", + "prevo": "litwick", + "evos": [ + "chandelure" + ], + "evoLevel": 41, + "eggGroups": [ + "Amorphous" + ] + }, + "chandelure": { + "num": 609, + "species": "Chandelure", + "types": [ + "Ghost", + "Fire" + ], + "baseStats": { + "hp": 60, + "atk": 55, + "def": 90, + "spa": 145, + "spd": 90, + "spe": 80 + }, + "abilities": { + "0": "Flash Fire", + "1": "Flame Body", + "H": "Infiltrator" + }, + "heightm": 1, + "weightkg": 34.3, + "color": "Black", + "prevo": "lampent", + "evoLevel": 41, + "eggGroups": [ + "Amorphous" + ] + }, + "axew": { + "num": 610, + "species": "Axew", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 46, + "atk": 87, + "def": 60, + "spa": 30, + "spd": 40, + "spe": 57 + }, + "abilities": { + "0": "Rivalry", + "1": "Mold Breaker", + "H": "Unnerve" + }, + "heightm": 0.6, + "weightkg": 18, + "color": "Green", + "evos": [ + "fraxure" + ], + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "fraxure": { + "num": 611, + "species": "Fraxure", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 66, + "atk": 117, + "def": 70, + "spa": 40, + "spd": 50, + "spe": 67 + }, + "abilities": { + "0": "Rivalry", + "1": "Mold Breaker", + "H": "Unnerve" + }, + "heightm": 1, + "weightkg": 36, + "color": "Green", + "prevo": "axew", + "evos": [ + "haxorus" + ], + "evoLevel": 38, + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "haxorus": { + "num": 612, + "species": "Haxorus", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 76, + "atk": 147, + "def": 90, + "spa": 60, + "spd": 70, + "spe": 97 + }, + "abilities": { + "0": "Rivalry", + "1": "Mold Breaker", + "H": "Unnerve" + }, + "heightm": 1.8, + "weightkg": 105.5, + "color": "Yellow", + "prevo": "fraxure", + "evoLevel": 48, + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "cubchoo": { + "num": 613, + "species": "Cubchoo", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 55, + "atk": 70, + "def": 40, + "spa": 60, + "spd": 40, + "spe": 40 + }, + "abilities": { + "0": "Snow Cloak", + "H": "Rattled" + }, + "heightm": 0.5, + "weightkg": 8.5, + "color": "White", + "evos": [ + "beartic" + ], + "eggGroups": [ + "Field" + ] + }, + "beartic": { + "num": 614, + "species": "Beartic", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 95, + "atk": 110, + "def": 80, + "spa": 70, + "spd": 80, + "spe": 50 + }, + "abilities": { + "0": "Snow Cloak", + "H": "Swift Swim" + }, + "heightm": 2.6, + "weightkg": 260, + "color": "White", + "prevo": "cubchoo", + "evoLevel": 37, + "eggGroups": [ + "Field" + ] + }, + "cryogonal": { + "num": 615, + "species": "Cryogonal", + "types": [ + "Ice" + ], + "gender": "N", + "baseStats": { + "hp": 70, + "atk": 50, + "def": 30, + "spa": 95, + "spd": 135, + "spe": 105 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.1, + "weightkg": 148, + "color": "Blue", + "eggGroups": [ + "Mineral" + ] + }, + "shelmet": { + "num": 616, + "species": "Shelmet", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 50, + "atk": 40, + "def": 85, + "spa": 40, + "spd": 65, + "spe": 25 + }, + "abilities": { + "0": "Hydration", + "1": "Shell Armor", + "H": "Overcoat" + }, + "heightm": 0.4, + "weightkg": 7.7, + "color": "Red", + "evos": [ + "accelgor" + ], + "eggGroups": [ + "Bug" + ] + }, + "accelgor": { + "num": 617, + "species": "Accelgor", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 80, + "atk": 70, + "def": 40, + "spa": 100, + "spd": 60, + "spe": 145 + }, + "abilities": { + "0": "Hydration", + "1": "Sticky Hold", + "H": "Unburden" + }, + "heightm": 0.8, + "weightkg": 25.3, + "color": "Red", + "prevo": "shelmet", + "evoLevel": 1, + "eggGroups": [ + "Bug" + ] + }, + "stunfisk": { + "num": 618, + "species": "Stunfisk", + "types": [ + "Ground", + "Electric" + ], + "baseStats": { + "hp": 109, + "atk": 66, + "def": 84, + "spa": 81, + "spd": 99, + "spe": 32 + }, + "abilities": { + "0": "Static", + "1": "Limber", + "H": "Sand Veil" + }, + "heightm": 0.7, + "weightkg": 11, + "color": "Brown", + "eggGroups": [ + "Water 1", + "Amorphous" + ] + }, + "mienfoo": { + "num": 619, + "species": "Mienfoo", + "types": [ + "Fighting" + ], + "baseStats": { + "hp": 45, + "atk": 85, + "def": 50, + "spa": 55, + "spd": 50, + "spe": 65 + }, + "abilities": { + "0": "Inner Focus", + "1": "Regenerator", + "H": "Reckless" + }, + "heightm": 0.9, + "weightkg": 20, + "color": "Yellow", + "evos": [ + "mienshao" + ], + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "mienshao": { + "num": 620, + "species": "Mienshao", + "types": [ + "Fighting" + ], + "baseStats": { + "hp": 65, + "atk": 125, + "def": 60, + "spa": 95, + "spd": 60, + "spe": 105 + }, + "abilities": { + "0": "Inner Focus", + "1": "Regenerator", + "H": "Reckless" + }, + "heightm": 1.4, + "weightkg": 35.5, + "color": "Purple", + "prevo": "mienfoo", + "evoLevel": 50, + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "druddigon": { + "num": 621, + "species": "Druddigon", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 77, + "atk": 120, + "def": 90, + "spa": 60, + "spd": 90, + "spe": 48 + }, + "abilities": { + "0": "Rough Skin", + "1": "Sheer Force", + "H": "Mold Breaker" + }, + "heightm": 1.6, + "weightkg": 139, + "color": "Red", + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "golett": { + "num": 622, + "species": "Golett", + "types": [ + "Ground", + "Ghost" + ], + "gender": "N", + "baseStats": { + "hp": 59, + "atk": 74, + "def": 50, + "spa": 35, + "spd": 50, + "spe": 35 + }, + "abilities": { + "0": "Iron Fist", + "1": "Klutz", + "H": "No Guard" + }, + "heightm": 1, + "weightkg": 92, + "color": "Green", + "evos": [ + "golurk" + ], + "eggGroups": [ + "Mineral" + ] + }, + "golurk": { + "num": 623, + "species": "Golurk", + "types": [ + "Ground", + "Ghost" + ], + "gender": "N", + "baseStats": { + "hp": 89, + "atk": 124, + "def": 80, + "spa": 55, + "spd": 80, + "spe": 55 + }, + "abilities": { + "0": "Iron Fist", + "1": "Klutz", + "H": "No Guard" + }, + "heightm": 2.8, + "weightkg": 330, + "color": "Green", + "prevo": "golett", + "evoLevel": 43, + "eggGroups": [ + "Mineral" + ] + }, + "pawniard": { + "num": 624, + "species": "Pawniard", + "types": [ + "Dark", + "Steel" + ], + "baseStats": { + "hp": 45, + "atk": 85, + "def": 70, + "spa": 40, + "spd": 40, + "spe": 60 + }, + "abilities": { + "0": "Defiant", + "1": "Inner Focus", + "H": "Pressure" + }, + "heightm": 0.5, + "weightkg": 10.2, + "color": "Red", + "evos": [ + "bisharp" + ], + "eggGroups": [ + "Human-Like" + ] + }, + "bisharp": { + "num": 625, + "species": "Bisharp", + "types": [ + "Dark", + "Steel" + ], + "baseStats": { + "hp": 65, + "atk": 125, + "def": 100, + "spa": 60, + "spd": 70, + "spe": 70 + }, + "abilities": { + "0": "Defiant", + "1": "Inner Focus", + "H": "Pressure" + }, + "heightm": 1.6, + "weightkg": 70, + "color": "Red", + "prevo": "pawniard", + "evoLevel": 52, + "eggGroups": [ + "Human-Like" + ] + }, + "bouffalant": { + "num": 626, + "species": "Bouffalant", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 95, + "atk": 110, + "def": 95, + "spa": 40, + "spd": 95, + "spe": 55 + }, + "abilities": { + "0": "Reckless", + "1": "Sap Sipper", + "H": "Soundproof" + }, + "heightm": 1.6, + "weightkg": 94.6, + "color": "Brown", + "eggGroups": [ + "Field" + ] + }, + "rufflet": { + "num": 627, + "species": "Rufflet", + "types": [ + "Normal", + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 70, + "atk": 83, + "def": 50, + "spa": 37, + "spd": 50, + "spe": 60 + }, + "abilities": { + "0": "Keen Eye", + "1": "Sheer Force", + "H": "Hustle" + }, + "heightm": 0.5, + "weightkg": 10.5, + "color": "White", + "evos": [ + "braviary" + ], + "eggGroups": [ + "Flying" + ] + }, + "braviary": { + "num": 628, + "species": "Braviary", + "types": [ + "Normal", + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 100, + "atk": 123, + "def": 75, + "spa": 57, + "spd": 75, + "spe": 80 + }, + "abilities": { + "0": "Keen Eye", + "1": "Sheer Force", + "H": "Defiant" + }, + "heightm": 1.5, + "weightkg": 41, + "color": "Red", + "prevo": "rufflet", + "evoLevel": 54, + "eggGroups": [ + "Flying" + ] + }, + "vullaby": { + "num": 629, + "species": "Vullaby", + "types": [ + "Dark", + "Flying" + ], + "gender": "F", + "baseStats": { + "hp": 70, + "atk": 55, + "def": 75, + "spa": 45, + "spd": 65, + "spe": 60 + }, + "abilities": { + "0": "Big Pecks", + "1": "Overcoat", + "H": "Weak Armor" + }, + "heightm": 0.5, + "weightkg": 9, + "color": "Brown", + "evos": [ + "mandibuzz" + ], + "eggGroups": [ + "Flying" + ] + }, + "mandibuzz": { + "num": 630, + "species": "Mandibuzz", + "types": [ + "Dark", + "Flying" + ], + "gender": "F", + "baseStats": { + "hp": 110, + "atk": 65, + "def": 105, + "spa": 55, + "spd": 95, + "spe": 80 + }, + "abilities": { + "0": "Big Pecks", + "1": "Overcoat", + "H": "Weak Armor" + }, + "heightm": 1.2, + "weightkg": 39.5, + "color": "Brown", + "prevo": "vullaby", + "evoLevel": 54, + "eggGroups": [ + "Flying" + ] + }, + "heatmor": { + "num": 631, + "species": "Heatmor", + "types": [ + "Fire" + ], + "baseStats": { + "hp": 85, + "atk": 97, + "def": 66, + "spa": 105, + "spd": 66, + "spe": 65 + }, + "abilities": { + "0": "Gluttony", + "1": "Flash Fire", + "H": "White Smoke" + }, + "heightm": 1.4, + "weightkg": 58, + "color": "Red", + "eggGroups": [ + "Field" + ] + }, + "durant": { + "num": 632, + "species": "Durant", + "types": [ + "Bug", + "Steel" + ], + "baseStats": { + "hp": 58, + "atk": 109, + "def": 112, + "spa": 48, + "spd": 48, + "spe": 109 + }, + "abilities": { + "0": "Swarm", + "1": "Hustle", + "H": "Truant" + }, + "heightm": 0.3, + "weightkg": 33, + "color": "Gray", + "eggGroups": [ + "Bug" + ] + }, + "deino": { + "num": 633, + "species": "Deino", + "types": [ + "Dark", + "Dragon" + ], + "baseStats": { + "hp": 52, + "atk": 65, + "def": 50, + "spa": 45, + "spd": 50, + "spe": 38 + }, + "abilities": { + "0": "Hustle" + }, + "heightm": 0.8, + "weightkg": 17.3, + "color": "Blue", + "evos": [ + "zweilous" + ], + "eggGroups": [ + "Dragon" + ] + }, + "zweilous": { + "num": 634, + "species": "Zweilous", + "types": [ + "Dark", + "Dragon" + ], + "baseStats": { + "hp": 72, + "atk": 85, + "def": 70, + "spa": 65, + "spd": 70, + "spe": 58 + }, + "abilities": { + "0": "Hustle" + }, + "heightm": 1.4, + "weightkg": 50, + "color": "Blue", + "prevo": "deino", + "evos": [ + "hydreigon" + ], + "evoLevel": 50, + "eggGroups": [ + "Dragon" + ] + }, + "hydreigon": { + "num": 635, + "species": "Hydreigon", + "types": [ + "Dark", + "Dragon" + ], + "baseStats": { + "hp": 92, + "atk": 105, + "def": 90, + "spa": 125, + "spd": 90, + "spe": 98 + }, + "abilities": { + "0": "Levitate" + }, + "heightm": 1.8, + "weightkg": 160, + "color": "Blue", + "prevo": "zweilous", + "evoLevel": 64, + "eggGroups": [ + "Dragon" + ] + }, + "larvesta": { + "num": 636, + "species": "Larvesta", + "types": [ + "Bug", + "Fire" + ], + "baseStats": { + "hp": 55, + "atk": 85, + "def": 55, + "spa": 50, + "spd": 55, + "spe": 60 + }, + "abilities": { + "0": "Flame Body", + "H": "Swarm" + }, + "heightm": 1.1, + "weightkg": 28.8, + "color": "White", + "evos": [ + "volcarona" + ], + "eggGroups": [ + "Bug" + ] + }, + "volcarona": { + "num": 637, + "species": "Volcarona", + "types": [ + "Bug", + "Fire" + ], + "baseStats": { + "hp": 85, + "atk": 60, + "def": 65, + "spa": 135, + "spd": 105, + "spe": 100 + }, + "abilities": { + "0": "Flame Body", + "H": "Swarm" + }, + "heightm": 1.6, + "weightkg": 46, + "color": "White", + "prevo": "larvesta", + "evoLevel": 59, + "eggGroups": [ + "Bug" + ] + }, + "cobalion": { + "num": 638, + "species": "Cobalion", + "types": [ + "Steel", + "Fighting" + ], + "gender": "N", + "baseStats": { + "hp": 91, + "atk": 90, + "def": 129, + "spa": 90, + "spd": 72, + "spe": 108 + }, + "abilities": { + "0": "Justified" + }, + "heightm": 2.1, + "weightkg": 250, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "terrakion": { + "num": 639, + "species": "Terrakion", + "types": [ + "Rock", + "Fighting" + ], + "gender": "N", + "baseStats": { + "hp": 91, + "atk": 129, + "def": 90, + "spa": 72, + "spd": 90, + "spe": 108 + }, + "abilities": { + "0": "Justified" + }, + "heightm": 1.9, + "weightkg": 260, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "virizion": { + "num": 640, + "species": "Virizion", + "types": [ + "Grass", + "Fighting" + ], + "gender": "N", + "baseStats": { + "hp": 91, + "atk": 90, + "def": 72, + "spa": 90, + "spd": 129, + "spe": 108 + }, + "abilities": { + "0": "Justified" + }, + "heightm": 2, + "weightkg": 200, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ] + }, + "tornadus": { + "num": 641, + "species": "Tornadus", + "baseForme": "Incarnate", + "types": [ + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 79, + "atk": 115, + "def": 70, + "spa": 125, + "spd": 80, + "spe": 111 + }, + "abilities": { + "0": "Prankster", + "H": "Defiant" + }, + "heightm": 1.5, + "weightkg": 63, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "tornadustherian" + ] + }, + "tornadustherian": { + "num": 641, + "species": "Tornadus-Therian", + "baseSpecies": "Tornadus", + "forme": "Therian", + "formeLetter": "T", + "types": [ + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 79, + "atk": 100, + "def": 80, + "spa": 110, + "spd": 90, + "spe": 121 + }, + "abilities": { + "0": "Regenerator" + }, + "heightm": 1.4, + "weightkg": 63, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ] + }, + "thundurus": { + "num": 642, + "species": "Thundurus", + "baseForme": "Incarnate", + "types": [ + "Electric", + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 79, + "atk": 115, + "def": 70, + "spa": 125, + "spd": 80, + "spe": 111 + }, + "abilities": { + "0": "Prankster", + "H": "Defiant" + }, + "heightm": 1.5, + "weightkg": 61, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "thundurustherian" + ] + }, + "thundurustherian": { + "num": 642, + "species": "Thundurus-Therian", + "baseSpecies": "Thundurus", + "forme": "Therian", + "formeLetter": "T", + "types": [ + "Electric", + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 79, + "atk": 105, + "def": 70, + "spa": 145, + "spd": 80, + "spe": 101 + }, + "abilities": { + "0": "Volt Absorb" + }, + "heightm": 3, + "weightkg": 61, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "reshiram": { + "num": 643, + "species": "Reshiram", + "types": [ + "Dragon", + "Fire" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 120, + "def": 100, + "spa": 150, + "spd": 120, + "spe": 90 + }, + "abilities": { + "0": "Turboblaze" + }, + "heightm": 3.2, + "weightkg": 330, + "color": "White", + "eggGroups": [ + "Undiscovered" + ] + }, + "zekrom": { + "num": 644, + "species": "Zekrom", + "types": [ + "Dragon", + "Electric" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 150, + "def": 120, + "spa": 120, + "spd": 100, + "spe": 90 + }, + "abilities": { + "0": "Teravolt" + }, + "heightm": 2.9, + "weightkg": 345, + "color": "Black", + "eggGroups": [ + "Undiscovered" + ] + }, + "landorus": { + "num": 645, + "species": "Landorus", + "baseForme": "Incarnate", + "types": [ + "Ground", + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 89, + "atk": 125, + "def": 90, + "spa": 115, + "spd": 80, + "spe": 101 + }, + "abilities": { + "0": "Sand Force", + "H": "Sheer Force" + }, + "heightm": 1.5, + "weightkg": 68, + "color": "Brown", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "landorustherian" + ] + }, + "landorustherian": { + "num": 645, + "species": "Landorus-Therian", + "baseSpecies": "Landorus", + "forme": "Therian", + "formeLetter": "T", + "types": [ + "Ground", + "Flying" + ], + "gender": "M", + "baseStats": { + "hp": 89, + "atk": 145, + "def": 90, + "spa": 105, + "spd": 80, + "spe": 91 + }, + "abilities": { + "0": "Intimidate" + }, + "heightm": 1.3, + "weightkg": 68, + "color": "Brown", + "eggGroups": [ + "Undiscovered" + ] + }, + "kyurem": { + "num": 646, + "species": "Kyurem", + "types": [ + "Dragon", + "Ice" + ], + "gender": "N", + "baseStats": { + "hp": 125, + "atk": 130, + "def": 90, + "spa": 130, + "spd": 90, + "spe": 95 + }, + "abilities": { + "0": "Pressure" + }, + "heightm": 3, + "weightkg": 325, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "kyuremblack", + "kyuremwhite" + ] + }, + "kyuremblack": { + "num": 646, + "species": "Kyurem-Black", + "baseSpecies": "Kyurem", + "forme": "Black", + "formeLetter": "B", + "types": [ + "Dragon", + "Ice" + ], + "gender": "N", + "baseStats": { + "hp": 125, + "atk": 170, + "def": 100, + "spa": 120, + "spd": 90, + "spe": 95 + }, + "abilities": { + "0": "Teravolt" + }, + "heightm": 3.3, + "weightkg": 325, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "kyuremwhite": { + "num": 646, + "species": "Kyurem-White", + "baseSpecies": "Kyurem", + "forme": "White", + "formeLetter": "W", + "types": [ + "Dragon", + "Ice" + ], + "gender": "N", + "baseStats": { + "hp": 125, + "atk": 120, + "def": 90, + "spa": 170, + "spd": 100, + "spe": 95 + }, + "abilities": { + "0": "Turboblaze" + }, + "heightm": 3.6, + "weightkg": 325, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "keldeo": { + "num": 647, + "species": "Keldeo", + "baseForme": "Ordinary", + "types": [ + "Water", + "Fighting" + ], + "gender": "N", + "baseStats": { + "hp": 91, + "atk": 72, + "def": 90, + "spa": 129, + "spd": 90, + "spe": 108 + }, + "abilities": { + "0": "Justified" + }, + "heightm": 1.4, + "weightkg": 48.5, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "keldeoresolute" + ] + }, + "keldeoresolute": { + "num": 647, + "species": "Keldeo-Resolute", + "baseSpecies": "Keldeo", + "forme": "Resolute", + "formeLetter": "R", + "types": [ + "Water", + "Fighting" + ], + "gender": "N", + "baseStats": { + "hp": 91, + "atk": 72, + "def": 90, + "spa": 129, + "spd": 90, + "spe": 108 + }, + "abilities": { + "0": "Justified" + }, + "heightm": 1.4, + "weightkg": 48.5, + "color": "Yellow", + "eggGroups": [ + "Undiscovered" + ] + }, + "meloetta": { + "num": 648, + "species": "Meloetta", + "baseForme": "Aria", + "types": [ + "Normal", + "Psychic" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 77, + "def": 77, + "spa": 128, + "spd": 128, + "spe": 90 + }, + "abilities": { + "0": "Serene Grace" + }, + "heightm": 0.6, + "weightkg": 6.5, + "color": "White", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "meloettapirouette" + ] + }, + "meloettapirouette": { + "num": 648, + "species": "Meloetta-Pirouette", + "baseSpecies": "Meloetta", + "forme": "Pirouette", + "formeLetter": "P", + "types": [ + "Normal", + "Fighting" + ], + "gender": "N", + "baseStats": { + "hp": 100, + "atk": 128, + "def": 90, + "spa": 77, + "spd": 77, + "spe": 128 + }, + "abilities": { + "0": "Serene Grace" + }, + "heightm": 0.6, + "weightkg": 6.5, + "color": "White", + "eggGroups": [ + "Undiscovered" + ] + }, + "genesect": { + "num": 649, + "species": "Genesect", + "types": [ + "Bug", + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 71, + "atk": 120, + "def": 95, + "spa": 120, + "spd": 95, + "spe": 99 + }, + "abilities": { + "0": "Download" + }, + "heightm": 1.5, + "weightkg": 82.5, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "genesectdouse", + "genesectshock", + "genesectburn", + "genesectchill" + ] + }, + "genesectdouse": { + "num": 649, + "species": "Genesect-Douse", + "baseSpecies": "Genesect", + "forme": "Douse", + "formeLetter": "D", + "types": [ + "Bug", + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 71, + "atk": 120, + "def": 95, + "spa": 120, + "spd": 95, + "spe": 99 + }, + "abilities": { + "0": "Download" + }, + "heightm": 1.5, + "weightkg": 82.5, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ] + }, + "genesectshock": { + "num": 649, + "species": "Genesect-Shock", + "baseSpecies": "Genesect", + "forme": "Shock", + "formeLetter": "S", + "types": [ + "Bug", + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 71, + "atk": 120, + "def": 95, + "spa": 120, + "spd": 95, + "spe": 99 + }, + "abilities": { + "0": "Download" + }, + "heightm": 1.5, + "weightkg": 82.5, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ] + }, + "genesectburn": { + "num": 649, + "species": "Genesect-Burn", + "baseSpecies": "Genesect", + "forme": "Burn", + "formeLetter": "B", + "types": [ + "Bug", + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 71, + "atk": 120, + "def": 95, + "spa": 120, + "spd": 95, + "spe": 99 + }, + "abilities": { + "0": "Download" + }, + "heightm": 1.5, + "weightkg": 82.5, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ] + }, + "genesectchill": { + "num": 649, + "species": "Genesect-Chill", + "baseSpecies": "Genesect", + "forme": "Chill", + "formeLetter": "C", + "types": [ + "Bug", + "Steel" + ], + "gender": "N", + "baseStats": { + "hp": 71, + "atk": 120, + "def": 95, + "spa": 120, + "spd": 95, + "spe": 99 + }, + "abilities": { + "0": "Download" + }, + "heightm": 1.5, + "weightkg": 82.5, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ] + }, + "chespin": { + "num": 650, + "species": "Chespin", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 56, + "atk": 61, + "def": 65, + "spa": 48, + "spd": 45, + "spe": 38 + }, + "abilities": { + "0": "Overgrow", + "H": "Bulletproof" + }, + "heightm": 0.4, + "weightkg": 9, + "color": "Green", + "evos": [ + "quilladin" + ], + "eggGroups": [ + "Field" + ] + }, + "quilladin": { + "num": 651, + "species": "Quilladin", + "types": [ + "Grass" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 61, + "atk": 78, + "def": 95, + "spa": 56, + "spd": 58, + "spe": 57 + }, + "abilities": { + "0": "Overgrow", + "H": "Bulletproof" + }, + "heightm": 0.7, + "weightkg": 29, + "color": "Green", + "prevo": "chespin", + "evos": [ + "chesnaught" + ], + "evoLevel": 16, + "eggGroups": [ + "Field" + ] + }, + "chesnaught": { + "num": 652, + "species": "Chesnaught", + "types": [ + "Grass", + "Fighting" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 88, + "atk": 107, + "def": 122, + "spa": 74, + "spd": 75, + "spe": 64 + }, + "abilities": { + "0": "Overgrow", + "H": "Bulletproof" + }, + "heightm": 1.6, + "weightkg": 90, + "color": "Green", + "prevo": "quilladin", + "evoLevel": 36, + "eggGroups": [ + "Field" + ] + }, + "fennekin": { + "num": 653, + "species": "Fennekin", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 40, + "atk": 45, + "def": 40, + "spa": 62, + "spd": 60, + "spe": 60 + }, + "abilities": { + "0": "Blaze", + "H": "Magician" + }, + "heightm": 0.4, + "weightkg": 9.4, + "color": "Red", + "evos": [ + "braixen" + ], + "eggGroups": [ + "Field" + ] + }, + "braixen": { + "num": 654, + "species": "Braixen", + "types": [ + "Fire" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 59, + "atk": 59, + "def": 58, + "spa": 90, + "spd": 70, + "spe": 73 + }, + "abilities": { + "0": "Blaze", + "H": "Magician" + }, + "heightm": 1, + "weightkg": 14.5, + "color": "Red", + "prevo": "fennekin", + "evos": [ + "delphox" + ], + "evoLevel": 16, + "eggGroups": [ + "Field" + ] + }, + "delphox": { + "num": 655, + "species": "Delphox", + "types": [ + "Fire", + "Psychic" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 75, + "atk": 69, + "def": 72, + "spa": 114, + "spd": 100, + "spe": 104 + }, + "abilities": { + "0": "Blaze", + "H": "Magician" + }, + "heightm": 1.5, + "weightkg": 39, + "color": "Red", + "prevo": "braixen", + "evoLevel": 36, + "eggGroups": [ + "Field" + ] + }, + "froakie": { + "num": 656, + "species": "Froakie", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 41, + "atk": 56, + "def": 40, + "spa": 62, + "spd": 44, + "spe": 71 + }, + "abilities": { + "0": "Torrent", + "H": "Protean" + }, + "heightm": 0.3, + "weightkg": 7, + "color": "Blue", + "evos": [ + "frogadier" + ], + "eggGroups": [ + "Water 1" + ] + }, + "frogadier": { + "num": 657, + "species": "Frogadier", + "types": [ + "Water" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 54, + "atk": 63, + "def": 52, + "spa": 83, + "spd": 56, + "spe": 97 + }, + "abilities": { + "0": "Torrent", + "H": "Protean" + }, + "heightm": 0.6, + "weightkg": 10.9, + "color": "Blue", + "prevo": "froakie", + "evos": [ + "greninja" + ], + "evoLevel": 16, + "eggGroups": [ + "Water 1" + ] + }, + "greninja": { + "num": 658, + "species": "Greninja", + "types": [ + "Water", + "Dark" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 72, + "atk": 95, + "def": 67, + "spa": 103, + "spd": 71, + "spe": 122 + }, + "abilities": { + "0": "Torrent", + "H": "Protean" + }, + "heightm": 1.5, + "weightkg": 40, + "color": "Blue", + "prevo": "frogadier", + "evoLevel": 36, + "eggGroups": [ + "Water 1" + ] + }, + "bunnelby": { + "num": 659, + "species": "Bunnelby", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 38, + "atk": 36, + "def": 38, + "spa": 32, + "spd": 36, + "spe": 57 + }, + "abilities": { + "0": "Pickup", + "1": "Cheek Pouch", + "H": "Huge Power" + }, + "heightm": 0.4, + "weightkg": 5, + "color": "Brown", + "evos": [ + "diggersby" + ], + "eggGroups": [ + "Field" + ] + }, + "diggersby": { + "num": 660, + "species": "Diggersby", + "types": [ + "Normal", + "Ground" + ], + "baseStats": { + "hp": 85, + "atk": 56, + "def": 77, + "spa": 50, + "spd": 77, + "spe": 78 + }, + "abilities": { + "0": "Pickup", + "1": "Cheek Pouch", + "H": "Huge Power" + }, + "heightm": 1, + "weightkg": 42.4, + "color": "Brown", + "prevo": "bunnelby", + "evoLevel": 20, + "eggGroups": [ + "Field" + ] + }, + "fletchling": { + "num": 661, + "species": "Fletchling", + "types": [ + "Normal", + "Flying" + ], + "baseStats": { + "hp": 45, + "atk": 50, + "def": 43, + "spa": 40, + "spd": 38, + "spe": 62 + }, + "abilities": { + "0": "Big Pecks", + "H": "Gale Wings" + }, + "heightm": 0.3, + "weightkg": 1.7, + "color": "Red", + "evos": [ + "fletchinder" + ], + "eggGroups": [ + "Flying" + ] + }, + "fletchinder": { + "num": 662, + "species": "Fletchinder", + "types": [ + "Fire", + "Flying" + ], + "baseStats": { + "hp": 62, + "atk": 73, + "def": 55, + "spa": 56, + "spd": 52, + "spe": 84 + }, + "abilities": { + "0": "Flame Body", + "H": "Gale Wings" + }, + "heightm": 0.7, + "weightkg": 16, + "color": "Red", + "prevo": "fletchling", + "evos": [ + "talonflame" + ], + "evoLevel": 17, + "eggGroups": [ + "Flying" + ] + }, + "talonflame": { + "num": 663, + "species": "Talonflame", + "types": [ + "Fire", + "Flying" + ], + "baseStats": { + "hp": 78, + "atk": 81, + "def": 71, + "spa": 74, + "spd": 69, + "spe": 126 + }, + "abilities": { + "0": "Flame Body", + "H": "Gale Wings" + }, + "heightm": 1.2, + "weightkg": 24.5, + "color": "Red", + "prevo": "fletchinder", + "evoLevel": 35, + "eggGroups": [ + "Flying" + ] + }, + "scatterbug": { + "num": 664, + "species": "Scatterbug", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 38, + "atk": 35, + "def": 40, + "spa": 27, + "spd": 25, + "spe": 35 + }, + "abilities": { + "0": "Shield Dust", + "1": "Compound Eyes", + "H": "Friend Guard" + }, + "heightm": 0.3, + "weightkg": 2.5, + "color": "Black", + "evos": [ + "spewpa" + ], + "eggGroups": [ + "Bug" + ] + }, + "spewpa": { + "num": 665, + "species": "Spewpa", + "types": [ + "Bug" + ], + "baseStats": { + "hp": 45, + "atk": 22, + "def": 60, + "spa": 27, + "spd": 30, + "spe": 29 + }, + "abilities": { + "0": "Shed Skin", + "H": "Friend Guard" + }, + "heightm": 0.3, + "weightkg": 8.4, + "color": "Black", + "prevo": "scatterbug", + "evos": [ + "vivillon" + ], + "evoLevel": 9, + "eggGroups": [ + "Bug" + ] + }, + "vivillon": { + "num": 666, + "species": "Vivillon", + "types": [ + "Bug", + "Flying" + ], + "baseStats": { + "hp": 80, + "atk": 52, + "def": 50, + "spa": 90, + "spd": 50, + "spe": 89 + }, + "abilities": { + "0": "Shield Dust", + "1": "Compound Eyes", + "H": "Friend Guard" + }, + "heightm": 1.2, + "weightkg": 17, + "color": "Black", + "prevo": "spewpa", + "evoLevel": 12, + "eggGroups": [ + "Bug" + ], + "otherForms": [ + "vivillonarchipelago", + "vivilloncontinental", + "vivillonelegant", + "vivillongarden", + "vivillonhighplains", + "vivillonicysnow", + "vivillonjungle", + "vivillonmarine", + "vivillonmodern", + "vivillonmonsoon", + "vivillonocean", + "vivillonpolar", + "vivillonriver", + "vivillonsandstorm", + "vivillonsavanna", + "vivillonsun", + "vivillontundra" + ] + }, + "litleo": { + "num": 667, + "species": "Litleo", + "types": [ + "Fire", + "Normal" + ], + "baseStats": { + "hp": 62, + "atk": 50, + "def": 58, + "spa": 73, + "spd": 54, + "spe": 72 + }, + "abilities": { + "0": "Rivalry", + "1": "Unnerve", + "H": "Moxie" + }, + "heightm": 0.6, + "weightkg": 13.5, + "color": "Brown", + "evos": [ + "pyroar" + ], + "eggGroups": [ + "Field" + ] + }, + "pyroar": { + "num": 668, + "species": "Pyroar", + "types": [ + "Fire", + "Normal" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 86, + "atk": 68, + "def": 72, + "spa": 109, + "spd": 66, + "spe": 106 + }, + "abilities": { + "0": "Rivalry", + "1": "Unnerve", + "H": "Moxie" + }, + "heightm": 1.5, + "weightkg": 81.5, + "color": "Brown", + "prevo": "litleo", + "evoLevel": 35, + "eggGroups": [ + "Field" + ] + }, + "flabebe": { + "num": 669, + "species": "Flabebe", + "types": [ + "Fairy" + ], + "gender": "F", + "baseStats": { + "hp": 44, + "atk": 38, + "def": 39, + "spa": 61, + "spd": 79, + "spe": 42 + }, + "abilities": { + "0": "Flower Veil", + "H": "Symbiosis" + }, + "heightm": 0.1, + "weightkg": 0.1, + "color": "White", + "evos": [ + "floette" + ], + "eggGroups": [ + "Fairy" + ], + "otherForms": [ + "flabebeblue", + "flabebeorange", + "flabebewhite", + "flabebeyellow" + ] + }, + "floette": { + "num": 670, + "species": "Floette", + "baseForme": "Red-Flower", + "types": [ + "Fairy" + ], + "gender": "F", + "baseStats": { + "hp": 54, + "atk": 45, + "def": 47, + "spa": 75, + "spd": 98, + "spe": 52 + }, + "abilities": { + "0": "Flower Veil", + "H": "Symbiosis" + }, + "heightm": 0.2, + "weightkg": 0.9, + "color": "White", + "prevo": "flabebe", + "evos": [ + "florges" + ], + "evoLevel": 19, + "eggGroups": [ + "Fairy" + ], + "otherForms": [ + "floetteblue", + "floetteorange", + "floettewhite", + "floetteyellow" + ], + "otherFormes": [ + "floetteeternal" + ] + }, + "floetteeternal": { + "num": 670, + "species": "Floette-Eternal", + "baseSpecies": "Floette", + "forme": "Eternal", + "formeLetter": "E", + "types": [ + "Fairy" + ], + "gender": "F", + "baseStats": { + "hp": 74, + "atk": 65, + "def": 67, + "spa": 125, + "spd": 128, + "spe": 92 + }, + "abilities": { + "0": "Flower Veil" + }, + "heightm": 0.2, + "weightkg": 0.9, + "color": "White", + "eggGroups": [ + "Undiscovered" + ] + }, + "florges": { + "num": 671, + "species": "Florges", + "types": [ + "Fairy" + ], + "gender": "F", + "baseStats": { + "hp": 78, + "atk": 65, + "def": 68, + "spa": 112, + "spd": 154, + "spe": 75 + }, + "abilities": { + "0": "Flower Veil", + "H": "Symbiosis" + }, + "heightm": 1.1, + "weightkg": 10, + "color": "White", + "prevo": "floette", + "evoLevel": 19, + "eggGroups": [ + "Fairy" + ], + "otherForms": [ + "florgesblue", + "florgesorange", + "florgeswhite", + "florgesyellow" + ] + }, + "skiddo": { + "num": 672, + "species": "Skiddo", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 66, + "atk": 65, + "def": 48, + "spa": 62, + "spd": 57, + "spe": 52 + }, + "abilities": { + "0": "Sap Sipper", + "H": "Grass Pelt" + }, + "heightm": 0.9, + "weightkg": 31, + "color": "Brown", + "evos": [ + "gogoat" + ], + "eggGroups": [ + "Field" + ] + }, + "gogoat": { + "num": 673, + "species": "Gogoat", + "types": [ + "Grass" + ], + "baseStats": { + "hp": 123, + "atk": 100, + "def": 62, + "spa": 97, + "spd": 81, + "spe": 68 + }, + "abilities": { + "0": "Sap Sipper", + "H": "Grass Pelt" + }, + "heightm": 1.7, + "weightkg": 91, + "color": "Brown", + "prevo": "skiddo", + "evoLevel": 32, + "eggGroups": [ + "Field" + ] + }, + "pancham": { + "num": 674, + "species": "Pancham", + "types": [ + "Fighting" + ], + "baseStats": { + "hp": 67, + "atk": 82, + "def": 62, + "spa": 46, + "spd": 48, + "spe": 43 + }, + "abilities": { + "0": "Iron Fist", + "1": "Mold Breaker", + "H": "Scrappy" + }, + "heightm": 0.6, + "weightkg": 8, + "color": "White", + "evos": [ + "pangoro" + ], + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "pangoro": { + "num": 675, + "species": "Pangoro", + "types": [ + "Fighting", + "Dark" + ], + "baseStats": { + "hp": 95, + "atk": 124, + "def": 78, + "spa": 69, + "spd": 71, + "spe": 58 + }, + "abilities": { + "0": "Iron Fist", + "1": "Mold Breaker", + "H": "Scrappy" + }, + "heightm": 2.1, + "weightkg": 136, + "color": "White", + "prevo": "pancham", + "evoLevel": 32, + "eggGroups": [ + "Field", + "Human-Like" + ] + }, + "furfrou": { + "num": 676, + "species": "Furfrou", + "types": [ + "Normal" + ], + "baseStats": { + "hp": 75, + "atk": 80, + "def": 60, + "spa": 65, + "spd": 90, + "spe": 102 + }, + "abilities": { + "0": "Fur Coat" + }, + "heightm": 1.2, + "weightkg": 28, + "color": "White", + "eggGroups": [ + "Field" + ] + }, + "espurr": { + "num": 677, + "species": "Espurr", + "types": [ + "Psychic" + ], + "baseStats": { + "hp": 62, + "atk": 48, + "def": 54, + "spa": 63, + "spd": 60, + "spe": 68 + }, + "abilities": { + "0": "Keen Eye", + "1": "Infiltrator", + "H": "Own Tempo" + }, + "heightm": 0.3, + "weightkg": 3.5, + "color": "Gray", + "evos": [ + "meowstic" + ], + "eggGroups": [ + "Field" + ] + }, + "meowstic": { + "num": 678, + "species": "Meowstic", + "baseForme": "M", + "types": [ + "Psychic" + ], + "gender": "M", + "baseStats": { + "hp": 74, + "atk": 48, + "def": 76, + "spa": 83, + "spd": 81, + "spe": 104 + }, + "abilities": { + "0": "Keen Eye", + "1": "Infiltrator", + "H": "Prankster" + }, + "heightm": 0.6, + "weightkg": 8.5, + "color": "White", + "prevo": "espurr", + "evoLevel": 25, + "eggGroups": [ + "Field" + ], + "otherFormes": [ + "meowsticf" + ] + }, + "meowsticf": { + "num": 678, + "species": "Meowstic-F", + "baseSpecies": "Meowstic", + "forme": "F", + "formeLetter": "F", + "types": [ + "Psychic" + ], + "gender": "F", + "baseStats": { + "hp": 74, + "atk": 48, + "def": 76, + "spa": 83, + "spd": 81, + "spe": 104 + }, + "abilities": { + "0": "Keen Eye", + "1": "Infiltrator", + "H": "Competitive" + }, + "heightm": 0.6, + "weightkg": 8.5, + "color": "White", + "prevo": "espurr", + "evoLevel": 25, + "eggGroups": [ + "Field" + ] + }, + "honedge": { + "num": 679, + "species": "Honedge", + "types": [ + "Steel", + "Ghost" + ], + "baseStats": { + "hp": 45, + "atk": 80, + "def": 100, + "spa": 35, + "spd": 37, + "spe": 28 + }, + "abilities": { + "0": "No Guard" + }, + "heightm": 0.8, + "weightkg": 2, + "color": "Brown", + "evos": [ + "doublade" + ], + "eggGroups": [ + "Mineral" + ] + }, + "doublade": { + "num": 680, + "species": "Doublade", + "types": [ + "Steel", + "Ghost" + ], + "baseStats": { + "hp": 59, + "atk": 110, + "def": 150, + "spa": 45, + "spd": 49, + "spe": 35 + }, + "abilities": { + "0": "No Guard" + }, + "heightm": 0.8, + "weightkg": 4.5, + "color": "Brown", + "prevo": "honedge", + "evos": [ + "aegislash" + ], + "evoLevel": 35, + "eggGroups": [ + "Mineral" + ] + }, + "aegislash": { + "num": 681, + "species": "Aegislash", + "baseForme": "Shield", + "types": [ + "Steel", + "Ghost" + ], + "baseStats": { + "hp": 60, + "atk": 50, + "def": 150, + "spa": 50, + "spd": 150, + "spe": 60 + }, + "abilities": { + "0": "Stance Change" + }, + "heightm": 1.7, + "weightkg": 53, + "color": "Brown", + "prevo": "doublade", + "evoLevel": 35, + "eggGroups": [ + "Mineral" + ], + "otherFormes": [ + "aegislashblade" + ] + }, + "aegislashblade": { + "num": 681, + "species": "Aegislash-Blade", + "baseSpecies": "Aegislash", + "forme": "Blade", + "formeLetter": "B", + "types": [ + "Steel", + "Ghost" + ], + "baseStats": { + "hp": 60, + "atk": 150, + "def": 50, + "spa": 150, + "spd": 50, + "spe": 60 + }, + "abilities": { + "0": "Stance Change" + }, + "heightm": 1.7, + "weightkg": 53, + "color": "Brown", + "prevo": "doublade", + "evoLevel": 35, + "eggGroups": [ + "Mineral" + ] + }, + "spritzee": { + "num": 682, + "species": "Spritzee", + "types": [ + "Fairy" + ], + "baseStats": { + "hp": 78, + "atk": 52, + "def": 60, + "spa": 63, + "spd": 65, + "spe": 23 + }, + "abilities": { + "0": "Healer", + "H": "Aroma Veil" + }, + "heightm": 0.2, + "weightkg": 0.5, + "color": "Pink", + "evos": [ + "aromatisse" + ], + "eggGroups": [ + "Fairy" + ] + }, + "aromatisse": { + "num": 683, + "species": "Aromatisse", + "types": [ + "Fairy" + ], + "baseStats": { + "hp": 101, + "atk": 72, + "def": 72, + "spa": 99, + "spd": 89, + "spe": 29 + }, + "abilities": { + "0": "Healer", + "H": "Aroma Veil" + }, + "heightm": 0.8, + "weightkg": 15.5, + "color": "Pink", + "prevo": "spritzee", + "evoLevel": 1, + "eggGroups": [ + "Fairy" + ] + }, + "swirlix": { + "num": 684, + "species": "Swirlix", + "types": [ + "Fairy" + ], + "baseStats": { + "hp": 62, + "atk": 48, + "def": 66, + "spa": 59, + "spd": 57, + "spe": 49 + }, + "abilities": { + "0": "Sweet Veil", + "H": "Unburden" + }, + "heightm": 0.4, + "weightkg": 3.5, + "color": "White", + "evos": [ + "slurpuff" + ], + "eggGroups": [ + "Fairy" + ] + }, + "slurpuff": { + "num": 685, + "species": "Slurpuff", + "types": [ + "Fairy" + ], + "baseStats": { + "hp": 82, + "atk": 80, + "def": 86, + "spa": 85, + "spd": 75, + "spe": 72 + }, + "abilities": { + "0": "Sweet Veil", + "H": "Unburden" + }, + "heightm": 0.8, + "weightkg": 5, + "color": "White", + "prevo": "swirlix", + "evoLevel": 1, + "eggGroups": [ + "Fairy" + ] + }, + "inkay": { + "num": 686, + "species": "Inkay", + "types": [ + "Dark", + "Psychic" + ], + "baseStats": { + "hp": 53, + "atk": 54, + "def": 53, + "spa": 37, + "spd": 46, + "spe": 45 + }, + "abilities": { + "0": "Contrary", + "1": "Suction Cups", + "H": "Infiltrator" + }, + "heightm": 0.4, + "weightkg": 3.5, + "color": "Blue", + "evos": [ + "malamar" + ], + "eggGroups": [ + "Water 1", + "Water 2" + ] + }, + "malamar": { + "num": 687, + "species": "Malamar", + "types": [ + "Dark", + "Psychic" + ], + "baseStats": { + "hp": 86, + "atk": 92, + "def": 88, + "spa": 68, + "spd": 75, + "spe": 73 + }, + "abilities": { + "0": "Contrary", + "1": "Suction Cups", + "H": "Infiltrator" + }, + "heightm": 1.5, + "weightkg": 47, + "color": "Blue", + "prevo": "inkay", + "evoLevel": 30, + "eggGroups": [ + "Water 1", + "Water 2" + ] + }, + "binacle": { + "num": 688, + "species": "Binacle", + "types": [ + "Rock", + "Water" + ], + "baseStats": { + "hp": 42, + "atk": 52, + "def": 67, + "spa": 39, + "spd": 56, + "spe": 50 + }, + "abilities": { + "0": "Tough Claws", + "1": "Sniper", + "H": "Pickpocket" + }, + "heightm": 0.5, + "weightkg": 31, + "color": "Brown", + "evos": [ + "barbaracle" + ], + "eggGroups": [ + "Water 3" + ] + }, + "barbaracle": { + "num": 689, + "species": "Barbaracle", + "types": [ + "Rock", + "Water" + ], + "baseStats": { + "hp": 72, + "atk": 105, + "def": 115, + "spa": 54, + "spd": 86, + "spe": 68 + }, + "abilities": { + "0": "Tough Claws", + "1": "Sniper", + "H": "Pickpocket" + }, + "heightm": 1.3, + "weightkg": 96, + "color": "Brown", + "prevo": "binacle", + "evoLevel": 39, + "eggGroups": [ + "Water 3" + ] + }, + "skrelp": { + "num": 690, + "species": "Skrelp", + "types": [ + "Poison", + "Water" + ], + "baseStats": { + "hp": 50, + "atk": 60, + "def": 60, + "spa": 60, + "spd": 60, + "spe": 30 + }, + "abilities": { + "0": "Poison Point", + "1": "Poison Touch", + "H": "Adaptability" + }, + "heightm": 0.5, + "weightkg": 7.3, + "color": "Brown", + "evos": [ + "dragalge" + ], + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "dragalge": { + "num": 691, + "species": "Dragalge", + "types": [ + "Poison", + "Dragon" + ], + "baseStats": { + "hp": 65, + "atk": 75, + "def": 90, + "spa": 97, + "spd": 123, + "spe": 44 + }, + "abilities": { + "0": "Poison Point", + "1": "Poison Touch", + "H": "Adaptability" + }, + "heightm": 1.8, + "weightkg": 81.5, + "color": "Brown", + "prevo": "skrelp", + "evoLevel": 48, + "eggGroups": [ + "Water 1", + "Dragon" + ] + }, + "clauncher": { + "num": 692, + "species": "Clauncher", + "types": [ + "Water" + ], + "baseStats": { + "hp": 50, + "atk": 53, + "def": 62, + "spa": 58, + "spd": 63, + "spe": 44 + }, + "abilities": { + "0": "Mega Launcher" + }, + "heightm": 0.5, + "weightkg": 8.3, + "color": "Blue", + "evos": [ + "clawitzer" + ], + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "clawitzer": { + "num": 693, + "species": "Clawitzer", + "types": [ + "Water" + ], + "baseStats": { + "hp": 71, + "atk": 73, + "def": 88, + "spa": 120, + "spd": 89, + "spe": 59 + }, + "abilities": { + "0": "Mega Launcher" + }, + "heightm": 1.3, + "weightkg": 35.3, + "color": "Blue", + "prevo": "clauncher", + "evoLevel": 37, + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "helioptile": { + "num": 694, + "species": "Helioptile", + "types": [ + "Electric", + "Normal" + ], + "baseStats": { + "hp": 44, + "atk": 38, + "def": 33, + "spa": 61, + "spd": 43, + "spe": 70 + }, + "abilities": { + "0": "Dry Skin", + "1": "Sand Veil", + "H": "Solar Power" + }, + "heightm": 0.5, + "weightkg": 6, + "color": "Yellow", + "evos": [ + "heliolisk" + ], + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "heliolisk": { + "num": 695, + "species": "Heliolisk", + "types": [ + "Electric", + "Normal" + ], + "baseStats": { + "hp": 62, + "atk": 55, + "def": 52, + "spa": 109, + "spd": 94, + "spe": 109 + }, + "abilities": { + "0": "Dry Skin", + "1": "Sand Veil", + "H": "Solar Power" + }, + "heightm": 1, + "weightkg": 21, + "color": "Yellow", + "prevo": "helioptile", + "evoLevel": 1, + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "tyrunt": { + "num": 696, + "species": "Tyrunt", + "types": [ + "Rock", + "Dragon" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 58, + "atk": 89, + "def": 77, + "spa": 45, + "spd": 45, + "spe": 48 + }, + "abilities": { + "0": "Strong Jaw", + "H": "Sturdy" + }, + "heightm": 0.8, + "weightkg": 26, + "color": "Brown", + "evos": [ + "tyrantrum" + ], + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "tyrantrum": { + "num": 697, + "species": "Tyrantrum", + "types": [ + "Rock", + "Dragon" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 82, + "atk": 121, + "def": 119, + "spa": 69, + "spd": 59, + "spe": 71 + }, + "abilities": { + "0": "Strong Jaw", + "H": "Rock Head" + }, + "heightm": 2.5, + "weightkg": 270, + "color": "Red", + "prevo": "tyrunt", + "evoLevel": 39, + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "amaura": { + "num": 698, + "species": "Amaura", + "types": [ + "Rock", + "Ice" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 77, + "atk": 59, + "def": 50, + "spa": 67, + "spd": 63, + "spe": 46 + }, + "abilities": { + "0": "Refrigerate", + "H": "Snow Warning" + }, + "heightm": 1.3, + "weightkg": 25.2, + "color": "Blue", + "evos": [ + "aurorus" + ], + "eggGroups": [ + "Monster" + ] + }, + "aurorus": { + "num": 699, + "species": "Aurorus", + "types": [ + "Rock", + "Ice" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 123, + "atk": 77, + "def": 72, + "spa": 99, + "spd": 92, + "spe": 58 + }, + "abilities": { + "0": "Refrigerate", + "H": "Snow Warning" + }, + "heightm": 2.7, + "weightkg": 225, + "color": "Blue", + "prevo": "amaura", + "evoLevel": 39, + "eggGroups": [ + "Monster" + ] + }, + "sylveon": { + "num": 700, + "species": "Sylveon", + "types": [ + "Fairy" + ], + "genderRatio": { + "M": 0.875, + "F": 0.125 + }, + "baseStats": { + "hp": 95, + "atk": 65, + "def": 65, + "spa": 110, + "spd": 130, + "spe": 60 + }, + "abilities": { + "0": "Cute Charm", + "H": "Pixilate" + }, + "heightm": 1, + "weightkg": 23.5, + "color": "Pink", + "prevo": "eevee", + "evoLevel": 2, + "eggGroups": [ + "Field" + ] + }, + "hawlucha": { + "num": 701, + "species": "Hawlucha", + "types": [ + "Fighting", + "Flying" + ], + "baseStats": { + "hp": 78, + "atk": 92, + "def": 75, + "spa": 74, + "spd": 63, + "spe": 118 + }, + "abilities": { + "0": "Limber", + "1": "Unburden", + "H": "Mold Breaker" + }, + "heightm": 0.8, + "weightkg": 21.5, + "color": "Green", + "eggGroups": [ + "Human-Like" + ] + }, + "dedenne": { + "num": 702, + "species": "Dedenne", + "types": [ + "Electric", + "Fairy" + ], + "baseStats": { + "hp": 67, + "atk": 58, + "def": 57, + "spa": 81, + "spd": 67, + "spe": 101 + }, + "abilities": { + "0": "Cheek Pouch", + "1": "Pickup", + "H": "Plus" + }, + "heightm": 0.2, + "weightkg": 2.2, + "color": "Yellow", + "eggGroups": [ + "Field", + "Fairy" + ] + }, + "carbink": { + "num": 703, + "species": "Carbink", + "types": [ + "Rock", + "Fairy" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 50, + "def": 150, + "spa": 50, + "spd": 150, + "spe": 50 + }, + "abilities": { + "0": "Clear Body", + "H": "Sturdy" + }, + "heightm": 0.3, + "weightkg": 5.7, + "color": "Gray", + "eggGroups": [ + "Fairy", + "Mineral" + ] + }, + "goomy": { + "num": 704, + "species": "Goomy", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 45, + "atk": 50, + "def": 35, + "spa": 55, + "spd": 75, + "spe": 40 + }, + "abilities": { + "0": "Sap Sipper", + "1": "Hydration", + "H": "Gooey" + }, + "heightm": 0.3, + "weightkg": 2.8, + "color": "Purple", + "evos": [ + "sliggoo" + ], + "eggGroups": [ + "Dragon" + ] + }, + "sliggoo": { + "num": 705, + "species": "Sliggoo", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 68, + "atk": 75, + "def": 53, + "spa": 83, + "spd": 113, + "spe": 60 + }, + "abilities": { + "0": "Sap Sipper", + "1": "Hydration", + "H": "Gooey" + }, + "heightm": 0.8, + "weightkg": 17.5, + "color": "Purple", + "prevo": "goomy", + "evos": [ + "goodra" + ], + "evoLevel": 40, + "eggGroups": [ + "Dragon" + ] + }, + "goodra": { + "num": 706, + "species": "Goodra", + "types": [ + "Dragon" + ], + "baseStats": { + "hp": 90, + "atk": 100, + "def": 70, + "spa": 110, + "spd": 150, + "spe": 80 + }, + "abilities": { + "0": "Sap Sipper", + "1": "Hydration", + "H": "Gooey" + }, + "heightm": 2, + "weightkg": 150.5, + "color": "Purple", + "prevo": "sliggoo", + "evoLevel": 50, + "eggGroups": [ + "Dragon" + ] + }, + "klefki": { + "num": 707, + "species": "Klefki", + "types": [ + "Steel", + "Fairy" + ], + "baseStats": { + "hp": 57, + "atk": 80, + "def": 91, + "spa": 80, + "spd": 87, + "spe": 75 + }, + "abilities": { + "0": "Prankster", + "H": "Magician" + }, + "heightm": 0.2, + "weightkg": 3, + "color": "Gray", + "eggGroups": [ + "Mineral" + ] + }, + "phantump": { + "num": 708, + "species": "Phantump", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 43, + "atk": 70, + "def": 48, + "spa": 50, + "spd": 60, + "spe": 38 + }, + "abilities": { + "0": "Natural Cure", + "1": "Frisk", + "H": "Harvest" + }, + "heightm": 0.4, + "weightkg": 7, + "color": "Brown", + "evos": [ + "trevenant" + ], + "eggGroups": [ + "Grass", + "Amorphous" + ] + }, + "trevenant": { + "num": 709, + "species": "Trevenant", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 85, + "atk": 110, + "def": 76, + "spa": 65, + "spd": 82, + "spe": 56 + }, + "abilities": { + "0": "Natural Cure", + "1": "Frisk", + "H": "Harvest" + }, + "heightm": 1.5, + "weightkg": 71, + "color": "Brown", + "prevo": "phantump", + "evoLevel": 1, + "eggGroups": [ + "Grass", + "Amorphous" + ] + }, + "pumpkaboo": { + "num": 710, + "species": "Pumpkaboo", + "baseForme": "Average", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 49, + "atk": 66, + "def": 70, + "spa": 44, + "spd": 55, + "spe": 51 + }, + "abilities": { + "0": "Pickup", + "1": "Frisk", + "H": "Insomnia" + }, + "heightm": 0.4, + "weightkg": 5, + "color": "Brown", + "evos": [ + "gourgeist" + ], + "eggGroups": [ + "Amorphous" + ], + "otherFormes": [ + "pumpkaboosmall", + "pumpkaboolarge", + "pumpkaboosuper" + ] + }, + "pumpkaboosmall": { + "num": 710, + "species": "Pumpkaboo-Small", + "baseSpecies": "Pumpkaboo", + "forme": "Small", + "formeLetter": "S", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 44, + "atk": 66, + "def": 70, + "spa": 44, + "spd": 55, + "spe": 56 + }, + "abilities": { + "0": "Pickup", + "1": "Frisk", + "H": "Insomnia" + }, + "heightm": 0.3, + "weightkg": 3.5, + "color": "Brown", + "evos": [ + "gourgeistsmall" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "pumpkaboolarge": { + "num": 710, + "species": "Pumpkaboo-Large", + "baseSpecies": "Pumpkaboo", + "forme": "Large", + "formeLetter": "L", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 54, + "atk": 66, + "def": 70, + "spa": 44, + "spd": 55, + "spe": 46 + }, + "abilities": { + "0": "Pickup", + "1": "Frisk", + "H": "Insomnia" + }, + "heightm": 0.5, + "weightkg": 7.5, + "color": "Brown", + "evos": [ + "gourgeistlarge" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "pumpkaboosuper": { + "num": 710, + "species": "Pumpkaboo-Super", + "baseSpecies": "Pumpkaboo", + "forme": "Super", + "formeLetter": "S", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 59, + "atk": 66, + "def": 70, + "spa": 44, + "spd": 55, + "spe": 41 + }, + "abilities": { + "0": "Pickup", + "1": "Frisk", + "H": "Insomnia" + }, + "heightm": 0.8, + "weightkg": 15, + "color": "Brown", + "evos": [ + "gourgeistsuper" + ], + "eggGroups": [ + "Amorphous" + ] + }, + "gourgeist": { + "num": 711, + "species": "Gourgeist", + "baseForme": "Average", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 65, + "atk": 90, + "def": 122, + "spa": 58, + "spd": 75, + "spe": 84 + }, + "abilities": { + "0": "Pickup", + "1": "Frisk", + "H": "Insomnia" + }, + "heightm": 0.9, + "weightkg": 12.5, + "color": "Brown", + "prevo": "pumpkaboo", + "evoLevel": 1, + "eggGroups": [ + "Amorphous" + ], + "otherFormes": [ + "gourgeistsmall", + "gourgeistlarge", + "gourgeistsuper" + ] + }, + "gourgeistsmall": { + "num": 711, + "species": "Gourgeist-Small", + "baseSpecies": "Gourgeist", + "forme": "Small", + "formeLetter": "S", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 55, + "atk": 85, + "def": 122, + "spa": 58, + "spd": 75, + "spe": 99 + }, + "abilities": { + "0": "Pickup", + "1": "Frisk", + "H": "Insomnia" + }, + "heightm": 0.7, + "weightkg": 9.5, + "color": "Brown", + "prevo": "pumpkaboosmall", + "evoLevel": 1, + "eggGroups": [ + "Amorphous" + ] + }, + "gourgeistlarge": { + "num": 711, + "species": "Gourgeist-Large", + "baseSpecies": "Gourgeist", + "forme": "Large", + "formeLetter": "L", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 75, + "atk": 95, + "def": 122, + "spa": 58, + "spd": 75, + "spe": 69 + }, + "abilities": { + "0": "Pickup", + "1": "Frisk", + "H": "Insomnia" + }, + "heightm": 1.1, + "weightkg": 14, + "color": "Brown", + "prevo": "pumpkaboolarge", + "evoLevel": 1, + "eggGroups": [ + "Amorphous" + ] + }, + "gourgeistsuper": { + "num": 711, + "species": "Gourgeist-Super", + "baseSpecies": "Gourgeist", + "forme": "Super", + "formeLetter": "S", + "types": [ + "Ghost", + "Grass" + ], + "baseStats": { + "hp": 85, + "atk": 100, + "def": 122, + "spa": 58, + "spd": 75, + "spe": 54 + }, + "abilities": { + "0": "Pickup", + "1": "Frisk", + "H": "Insomnia" + }, + "heightm": 1.7, + "weightkg": 39, + "color": "Brown", + "prevo": "pumpkaboosuper", + "evoLevel": 1, + "eggGroups": [ + "Amorphous" + ] + }, + "bergmite": { + "num": 712, + "species": "Bergmite", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 55, + "atk": 69, + "def": 85, + "spa": 32, + "spd": 35, + "spe": 28 + }, + "abilities": { + "0": "Own Tempo", + "1": "Ice Body", + "H": "Sturdy" + }, + "heightm": 1, + "weightkg": 99.5, + "color": "Blue", + "evos": [ + "avalugg" + ], + "eggGroups": [ + "Monster" + ] + }, + "avalugg": { + "num": 713, + "species": "Avalugg", + "types": [ + "Ice" + ], + "baseStats": { + "hp": 95, + "atk": 117, + "def": 184, + "spa": 44, + "spd": 46, + "spe": 28 + }, + "abilities": { + "0": "Own Tempo", + "1": "Ice Body", + "H": "Sturdy" + }, + "heightm": 2, + "weightkg": 505, + "color": "Blue", + "prevo": "bergmite", + "evoLevel": 37, + "eggGroups": [ + "Monster" + ] + }, + "noibat": { + "num": 714, + "species": "Noibat", + "types": [ + "Flying", + "Dragon" + ], + "baseStats": { + "hp": 40, + "atk": 30, + "def": 35, + "spa": 45, + "spd": 40, + "spe": 55 + }, + "abilities": { + "0": "Frisk", + "1": "Infiltrator", + "H": "Telepathy" + }, + "heightm": 0.5, + "weightkg": 8, + "color": "Purple", + "evos": [ + "noivern" + ], + "eggGroups": [ + "Flying" + ] + }, + "noivern": { + "num": 715, + "species": "Noivern", + "types": [ + "Flying", + "Dragon" + ], + "baseStats": { + "hp": 85, + "atk": 70, + "def": 80, + "spa": 97, + "spd": 80, + "spe": 123 + }, + "abilities": { + "0": "Frisk", + "1": "Infiltrator", + "H": "Telepathy" + }, + "heightm": 1.5, + "weightkg": 85, + "color": "Purple", + "prevo": "noibat", + "evoLevel": 48, + "eggGroups": [ + "Flying" + ] + }, + "xerneas": { + "num": 716, + "species": "Xerneas", + "types": [ + "Fairy" + ], + "gender": "N", + "baseStats": { + "hp": 126, + "atk": 131, + "def": 95, + "spa": 131, + "spd": 98, + "spe": 99 + }, + "abilities": { + "0": "Fairy Aura" + }, + "heightm": 3, + "weightkg": 215, + "color": "Blue", + "eggGroups": [ + "Undiscovered" + ] + }, + "yveltal": { + "num": 717, + "species": "Yveltal", + "types": [ + "Dark", + "Flying" + ], + "gender": "N", + "baseStats": { + "hp": 126, + "atk": 131, + "def": 95, + "spa": 131, + "spd": 98, + "spe": 99 + }, + "abilities": { + "0": "Dark Aura" + }, + "heightm": 5.8, + "weightkg": 203, + "color": "Red", + "eggGroups": [ + "Undiscovered" + ] + }, + "zygarde": { + "num": 718, + "species": "Zygarde", + "types": [ + "Dragon", + "Ground" + ], + "gender": "N", + "baseStats": { + "hp": 108, + "atk": 100, + "def": 121, + "spa": 81, + "spd": 95, + "spe": 95 + }, + "abilities": { + "0": "Aura Break" + }, + "heightm": 5, + "weightkg": 305, + "color": "Green", + "eggGroups": [ + "Undiscovered" + ] + }, + "diancie": { + "num": 719, + "species": "Diancie", + "types": [ + "Rock", + "Fairy" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 100, + "def": 150, + "spa": 100, + "spd": 150, + "spe": 50 + }, + "abilities": { + "0": "Clear Body" + }, + "heightm": 0.7, + "weightkg": 8.8, + "color": "Pink", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "dianciemega" + ] + }, + "dianciemega": { + "num": 719, + "species": "Diancie-Mega", + "baseSpecies": "Diancie", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Rock", + "Fairy" + ], + "gender": "N", + "baseStats": { + "hp": 50, + "atk": 160, + "def": 110, + "spa": 160, + "spd": 110, + "spe": 110 + }, + "abilities": { + "0": "Magic Bounce" + }, + "heightm": 1.1, + "weightkg": 27.8, + "color": "Pink", + "eggGroups": [ + "Undiscovered" + ] + }, + "hoopa": { + "num": 720, + "species": "Hoopa", + "baseForme": "Confined", + "types": [ + "Psychic", + "Ghost" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 110, + "def": 60, + "spa": 150, + "spd": 130, + "spe": 70 + }, + "abilities": { + "0": "Magician" + }, + "heightm": 0.5, + "weightkg": 9, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ], + "otherFormes": [ + "hoopaunbound" + ] + }, + "hoopaunbound": { + "num": 720, + "species": "Hoopa-Unbound", + "baseSpecies": "Hoopa", + "forme": "Unbound", + "formeLetter": "U", + "types": [ + "Psychic", + "Dark" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 160, + "def": 60, + "spa": 170, + "spd": 130, + "spe": 80 + }, + "abilities": { + "0": "Magician" + }, + "heightm": 6.5, + "weightkg": 490, + "color": "Purple", + "eggGroups": [ + "Undiscovered" + ] + }, + "volcanion": { + "num": 721, + "species": "Volcanion", + "types": [ + "Fire", + "Water" + ], + "gender": "N", + "baseStats": { + "hp": 80, + "atk": 110, + "def": 120, + "spa": 130, + "spd": 90, + "spe": 70 + }, + "abilities": { + "0": "Water Absorb" + }, + "heightm": 1.7, + "weightkg": 195, + "color": "Brown", + "eggGroups": [ + "Undiscovered" + ] + }, + "missingno": { + "num": 0, + "species": "Missingno.", + "types": [ + "Bird", + "Normal" + ], + "baseStats": { + "hp": 33, + "atk": 136, + "def": 0, + "spa": 6, + "spd": 6, + "spe": 29 + }, + "abilities": { + "0": "", + "H": "" + }, + "heightm": 3, + "weightkg": 1590.8, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "tomohawk": { + "num": -1, + "species": "Tomohawk", + "types": [ + "Flying", + "Fighting" + ], + "baseStats": { + "hp": 105, + "atk": 60, + "def": 90, + "spa": 115, + "spd": 80, + "spe": 85 + }, + "abilities": { + "0": "Intimidate", + "1": "Prankster", + "H": "Justified" + }, + "heightm": 1.27, + "weightkg": 37.2, + "color": "Red", + "eggGroups": [ + "Field", + "Flying" + ] + }, + "necturna": { + "num": -2, + "species": "Necturna", + "types": [ + "Grass", + "Ghost" + ], + "gender": "F", + "baseStats": { + "hp": 64, + "atk": 120, + "def": 100, + "spa": 85, + "spd": 120, + "spe": 81 + }, + "abilities": { + "0": "Forewarn", + "H": "Telepathy" + }, + "heightm": 1.65, + "weightkg": 49.6, + "color": "Black", + "eggGroups": [ + "Grass", + "Field" + ] + }, + "mollux": { + "num": -3, + "species": "Mollux", + "types": [ + "Fire", + "Poison" + ], + "baseStats": { + "hp": 95, + "atk": 45, + "def": 83, + "spa": 131, + "spd": 105, + "spe": 76 + }, + "abilities": { + "0": "Dry Skin", + "H": "Illuminate" + }, + "heightm": 1.2, + "weightkg": 41, + "color": "Pink", + "eggGroups": [ + "Fairy", + "Field" + ] + }, + "aurumoth": { + "num": -4, + "species": "Aurumoth", + "types": [ + "Bug", + "Psychic" + ], + "baseStats": { + "hp": 110, + "atk": 120, + "def": 99, + "spa": 117, + "spd": 60, + "spe": 94 + }, + "abilities": { + "0": "Weak Armor", + "1": "No Guard", + "H": "Illusion" + }, + "heightm": 2.1, + "weightkg": 193, + "color": "Purple", + "eggGroups": [ + "Bug" + ] + }, + "malaconda": { + "num": -5, + "species": "Malaconda", + "types": [ + "Dark", + "Grass" + ], + "baseStats": { + "hp": 115, + "atk": 100, + "def": 60, + "spa": 40, + "spd": 130, + "spe": 55 + }, + "abilities": { + "0": "Harvest", + "1": "Infiltrator" + }, + "heightm": 5.5, + "weightkg": 108.8, + "color": "Brown", + "eggGroups": [ + "Grass", + "Dragon" + ] + }, + "cawmodore": { + "num": -6, + "species": "Cawmodore", + "types": [ + "Steel", + "Flying" + ], + "baseStats": { + "hp": 50, + "atk": 92, + "def": 130, + "spa": 65, + "spd": 75, + "spe": 118 + }, + "abilities": { + "0": "Intimidate", + "1": "Volt Absorb", + "H": "Big Pecks" + }, + "heightm": 1.7, + "weightkg": 37, + "color": "Black", + "eggGroups": [ + "Flying" + ] + }, + "volkraken": { + "num": -7, + "species": "Volkraken", + "types": [ + "Water", + "Fire" + ], + "baseStats": { + "hp": 100, + "atk": 45, + "def": 80, + "spa": 135, + "spd": 100, + "spe": 95 + }, + "abilities": { + "0": "Analytic", + "1": "Infiltrator", + "H": "Pressure" + }, + "heightm": 1.3, + "weightkg": 44.5, + "color": "Red", + "eggGroups": [ + "Water 1", + "Water 2" + ] + }, + "plasmanta": { + "num": -8, + "species": "Plasmanta", + "types": [ + "Electric", + "Poison" + ], + "baseStats": { + "hp": 60, + "atk": 57, + "def": 119, + "spa": 131, + "spd": 98, + "spe": 100 + }, + "abilities": { + "0": "Storm Drain", + "1": "Vital Spirit", + "H": "Telepathy" + }, + "heightm": 7, + "weightkg": 460, + "color": "Purple", + "eggGroups": [ + "Water 1", + "Water 2" + ] + }, + "naviathan": { + "num": -9, + "species": "Naviathan", + "types": [ + "Water", + "Steel" + ], + "baseStats": { + "hp": 103, + "atk": 110, + "def": 90, + "spa": 95, + "spd": 65, + "spe": 97 + }, + "abilities": { + "0": "Water Veil", + "1": "Heatproof", + "H": "Light Metal" + }, + "heightm": 3, + "weightkg": 510, + "color": "Gray", + "eggGroups": [ + "Water 1", + "Field" + ] + }, + "crucibelle": { + "num": -10, + "species": "Crucibelle", + "types": [ + "Rock", + "Poison" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 106, + "atk": 105, + "def": 65, + "spa": 75, + "spd": 85, + "spe": 104 + }, + "abilities": { + "0": "Regenerator", + "1": "Mold Breaker", + "H": "Liquid Ooze" + }, + "heightm": 1.3, + "weightkg": 23.6, + "color": "Purple", + "eggGroups": [ + "Amorphous", + "Mineral" + ], + "otherFormes": [ + "crucibellemega" + ] + }, + "crucibellemega": { + "num": -10, + "species": "Crucibelle-Mega", + "baseSpecies": "Crucibelle", + "forme": "Mega", + "formeLetter": "M", + "types": [ + "Rock", + "Poison" + ], + "genderRatio": { + "M": 0.25, + "F": 0.75 + }, + "baseStats": { + "hp": 106, + "atk": 135, + "def": 75, + "spa": 85, + "spd": 125, + "spe": 114 + }, + "abilities": { + "0": "Magic Guard" + }, + "heightm": 1.4, + "weightkg": 22.5, + "color": "Purple", + "eggGroups": [ + "Amorphous", + "Mineral" + ] + }, + "syclant": { + "num": -51, + "species": "Syclant", + "types": [ + "Ice", + "Bug" + ], + "baseStats": { + "hp": 70, + "atk": 116, + "def": 70, + "spa": 114, + "spd": 64, + "spe": 121 + }, + "abilities": { + "0": "Compound Eyes", + "1": "Mountaineer" + }, + "heightm": 1.7, + "weightkg": 52, + "color": "Blue", + "eggGroups": [ + "Bug" + ] + }, + "revenankh": { + "num": -52, + "species": "Revenankh", + "types": [ + "Ghost", + "Fighting" + ], + "baseStats": { + "hp": 90, + "atk": 105, + "def": 90, + "spa": 65, + "spd": 110, + "spe": 65 + }, + "abilities": { + "0": "Shed Skin", + "1": "Air Lock" + }, + "heightm": 1.8, + "weightkg": 44, + "color": "White", + "eggGroups": [ + "Amorphous", + "Human-Like" + ] + }, + "pyroak": { + "num": -53, + "species": "Pyroak", + "types": [ + "Fire", + "Grass" + ], + "baseStats": { + "hp": 120, + "atk": 70, + "def": 105, + "spa": 95, + "spd": 90, + "spe": 60 + }, + "abilities": { + "0": "Rock Head", + "1": "Battle Armor" + }, + "heightm": 2.1, + "weightkg": 168, + "color": "Brown", + "eggGroups": [ + "Monster", + "Dragon" + ] + }, + "fidgit": { + "num": -54, + "species": "Fidgit", + "types": [ + "Poison", + "Ground" + ], + "baseStats": { + "hp": 95, + "atk": 76, + "def": 109, + "spa": 90, + "spd": 80, + "spe": 105 + }, + "abilities": { + "0": "Persistent", + "1": "Vital Spirit" + }, + "heightm": 0.9, + "weightkg": 53, + "color": "Purple", + "eggGroups": [ + "Field" + ] + }, + "stratagem": { + "num": -55, + "species": "Stratagem", + "types": [ + "Rock" + ], + "gender": "N", + "baseStats": { + "hp": 90, + "atk": 60, + "def": 65, + "spa": 120, + "spd": 70, + "spe": 130 + }, + "abilities": { + "0": "Levitate", + "1": "Technician" + }, + "heightm": 0.9, + "weightkg": 45, + "color": "Gray", + "eggGroups": [ + "Undiscovered" + ] + }, + "arghonaut": { + "num": -56, + "species": "Arghonaut", + "types": [ + "Water", + "Fighting" + ], + "baseStats": { + "hp": 105, + "atk": 110, + "def": 95, + "spa": 70, + "spd": 100, + "spe": 75 + }, + "abilities": { + "0": "Unaware" + }, + "heightm": 1.7, + "weightkg": 151, + "color": "Green", + "eggGroups": [ + "Water 1", + "Water 3" + ] + }, + "kitsunoh": { + "num": -57, + "species": "Kitsunoh", + "types": [ + "Steel", + "Ghost" + ], + "baseStats": { + "hp": 80, + "atk": 103, + "def": 85, + "spa": 55, + "spd": 80, + "spe": 110 + }, + "abilities": { + "0": "Frisk", + "1": "Limber" + }, + "heightm": 1.1, + "weightkg": 51, + "color": "Gray", + "eggGroups": [ + "Field" + ] + }, + "cyclohm": { + "num": -58, + "species": "Cyclohm", + "types": [ + "Electric", + "Dragon" + ], + "baseStats": { + "hp": 108, + "atk": 60, + "def": 118, + "spa": 112, + "spd": 70, + "spe": 80 + }, + "abilities": { + "0": "Shield Dust", + "1": "Static" + }, + "heightm": 1.6, + "weightkg": 59, + "color": "Yellow", + "eggGroups": [ + "Dragon", + "Monster" + ] + }, + "colossoil": { + "num": -59, + "species": "Colossoil", + "types": [ + "Dark", + "Ground" + ], + "baseStats": { + "hp": 133, + "atk": 122, + "def": 72, + "spa": 71, + "spd": 72, + "spe": 95 + }, + "abilities": { + "0": "Rebound", + "1": "Guts" + }, + "heightm": 2.6, + "weightkg": 683.6, + "color": "Brown", + "eggGroups": [ + "Water 2", + "Field" + ] + }, + "krilowatt": { + "num": -60, + "species": "Krilowatt", + "types": [ + "Electric", + "Water" + ], + "baseStats": { + "hp": 151, + "atk": 84, + "def": 73, + "spa": 83, + "spd": 74, + "spe": 105 + }, + "abilities": { + "0": "Trace", + "1": "Magic Guard" + }, + "heightm": 0.7, + "weightkg": 10.6, + "color": "Red", + "eggGroups": [ + "Water 1", + "Fairy" + ] + }, + "voodoom": { + "num": -61, + "species": "Voodoom", + "types": [ + "Fighting", + "Dark" + ], + "baseStats": { + "hp": 90, + "atk": 85, + "def": 80, + "spa": 105, + "spd": 80, + "spe": 110 + }, + "abilities": { + "0": "Volt Absorb", + "1": "Lightning Rod" + }, + "heightm": 2, + "weightkg": 75.5, + "color": "Brown", + "eggGroups": [ + "Human-Like", + "Ground" + ] + } +} \ No newline at end of file diff --git a/NadekoBot/bin/Debug/data/questions.json b/WizBot/bin/Debug/data/questions.json similarity index 100% rename from NadekoBot/bin/Debug/data/questions.json rename to WizBot/bin/Debug/data/questions.json diff --git a/NadekoBot/bin/Debug/data/quotes.json b/WizBot/bin/Debug/data/quotes.json similarity index 100% rename from NadekoBot/bin/Debug/data/quotes.json rename to WizBot/bin/Debug/data/quotes.json diff --git a/WizBot/bin/Debug/data/wowjokes.json b/WizBot/bin/Debug/data/wowjokes.json new file mode 100644 index 000000000..464b56d64 --- /dev/null +++ b/WizBot/bin/Debug/data/wowjokes.json @@ -0,0 +1,302 @@ +[ + { + "Question": "What do you call a gnome priest", + "Answer": "A compact disc" + }, + { + "Question": "Why does the best raiding guild smell so bad?", + "Answer": "because they never wipe" + }, + { + "Question": "Why are boomkins neither overpowered or underpowered?", + "Answer": "Because they're Balanced" + }, + { + "Question": "Who is George of the Jungle's other brother?", + "Answer": "Mark of the Wild." + }, + { + "Question": "What do you call a masturbating Tauren?", + "Answer": "Beef Stroganoff." + }, + { + "Question": "What's a rogue's favourite drink?", + "Answer": "Subtle Tea" + }, + { + "Question": "Classic: What to noobs and Rogues have in common?", + "Answer": "They both pick locks!" + }, + { + "Question": "What did Gul'dan do when he tripped?", + "Answer": "He fel" + }, + { + "Question": "Why didn't the warrior cross the road?", + "Answer": "No Path Available." + }, + { + "Question": "What are the chances we have gotten the last content patch before a new expansion?", + "Answer": "Slim Tanaan" + }, + { + "Question": "A trolls greeting is usually \"Eyy mon\" but what is a goblins?", + "Answer": "\"\"Mon-eyy\"\n\n^^^^^I'm ^^^^^so ^^^^^sorry..." + }, + { + "Question": "What musician is from ICC?", + "Answer": "What musician is from ICC?\n\nAn Arthas formally known as Prince." + }, + { + "Question": "How do you know if someone's been playing since Vanilla?", + "Answer": "Don't worry, they'll tell you" + }, + { + "Question": "What happens when a demon forgets his sunscreen?", + "Answer": "He ends up with a Burning Region" + }, + { + "Question": "Why are raid guilds recruiting rogues for the illidan fight?", + "Answer": "Because they are the only ones that have the required [preparation](http://www.wowhead.com/spell=14185/preparation) " + }, + { + "Question": "How does Naxxramas fly?", + "Answer": "with it's four wings" + }, + { + "Question": "Why is the Cenarion Circle neutral?", + "Answer": "Circles have no sides." + }, + { + "Question": "Why are rogues the best bar tenders?", + "Answer": "Because they always have cheap shots " + }, + { + "Question": "What does Gul'dan like on his hotdogs?", + "Answer": "EVERYTHIIING" + }, + { + "Question": "Why did a Felhunter win the spelling competition?", + "Answer": "Nobody else could Spell Lock." + }, + { + "Question": "What do you call it when Illidan teaches demon hunters in Legion?", + "Answer": "a 'demon-stration\"" + }, + { + "Question": "Why can't Paladins work out at Planet Fitness?", + "Answer": "Because it's a Judgement-free zone. " + }, + { + "Question": "Why didn't the rogue like his dagger upgrade?", + "Answer": "He wasn't a fan of knives." + }, + { + "Question": "What do you call a Gilnean church?", + "Answer": "Worgenized religion" + }, + { + "Question": "Why were night elves chosen to be Azeroth's first astronauts?", + "Answer": "Because they know the moon well." + }, + { + "Question": "Why are gnomes unable to be paladins?", + "Answer": "Because they cannot reach the Light." + }, + { + "Question": "Did you hear about the resto druid who never plays guardian?", + "Answer": "You could say he's all bark, no bite." + }, + { + "Question": "What's a dwarf rogue's favorite car?", + "Answer": "Dodge Ram" + }, + { + "Question": "Why can you only wear 1 shoe in the Emerald nightmare?", + "Answer": "Because they'll steal Ursoc." + }, + { + "Question": "Why is it impossible for a paladin and a rogue to make a baby?", + "Answer": "Because paladins use protection and rogues do it from behind.\n\n\nTaken from Sodapoppin's stream on December 1st. " + }, + { + "Question": "Do you know how Illidan hurt his knee?", + "Answer": "(this one came through in a GM ticket response today)\n\nDo you know how Illidan hurt his knee?\nIt's simple really...\nHe fel." + }, + { + "Question": "Icecrown Citadel was a pretty cool raid.", + "Answer": "Even thinking about it gives me the chills." + }, + { + "Question": "Why do Blood Elves tan so quickly?", + "Answer": "They use the Sun Well." + }, + { + "Question": "What is Bolvar doing lately?", + "Answer": "Just chillin." + }, + { + "Question": "What do you call 4 Mogu rolling down a hill?", + "Answer": "The Rolling Stones." + }, + { + "Question": "What is Taran Zhu's favorite cooking utensil?", + "Answer": "The Shado-Pan" + }, + { + "Question": "What do you call a vapid celebrity female night elf?", + "Answer": "Kim Darnassian" + }, + { + "Question": "Warlords of Draenor was an emotional expansion.", + "Answer": "Even the raids were in tiers." + }, + { + "Question": "Why do hunters never get married?", + "Answer": "Because they're always dis-engaging." + }, + { + "Question": "What do you call a kind warlock", + "Answer": "Affection lock" + }, + { + "Question": "Which dragon has the dream job?", + "Answer": "Ysera" + }, + { + "Question": "Healers are like artists", + "Answer": "No one appreciates them until they are dead" + }, + { + "Question": "What did Illidan say when one of his group mates' gear was broken?", + "Answer": "\"YOU ARE NOT REPAIRED!\"" + }, + { + "Question": "Why do bars hate rogues?", + "Answer": "Because they only want cheap shots." + }, + { + "Question": "What did the game tell the hunter when he dinged 40?", + "Answer": "You've got mail." + }, + { + "Question": "What do you call a Blood Elf who eats his vegetables?", + "Answer": "Kale'Thas" + }, + { + "Question": "Where do murlocs store their gold and treasure?", + "Answer": "At the River Bank!" + }, + { + "Question": "Why did the undead smell bad?", + "Answer": "He had no nose." + }, + { + "Question": "How did the Rogue one-shot Illidan?", + "Answer": "By using Preparation." + }, + { + "Question": "What's the first thing Illidan sees when he wakes up?", + "Answer": "NOTHING" + }, + { + "Question": "Why are all paladins so clean?", + "Answer": "...Because they are always taking Bubble baths!" + }, + { + "Question": "did you hear about the monk serial killer", + "Answer": "the murders were pre-meditated" + }, + { + "Question": "How much does a serving of Pandaren Cuisine weigh?", + "Answer": "About wonton." + }, + { + "Question": "What injury did Gul'Dan get from playing Tennis.", + "Answer": "Twisted Nethers." + }, + { + "Question": "A patch day joke...", + "Answer": "" + }, + { + "Question": "Yo mamma so fat....", + "Answer": "I tried to shadowstep her and I got a loading screen!" + }, + { + "Question": "What do you call it when the Argent Tournament catches on fire?", + "Answer": "Burning Crusade." + }, + { + "Question": "Why can you never take a good photo of a hunter?", + "Answer": "They are always out of focus" + }, + { + "Question": "How do impoverished Warriors get their weapons?", + "Answer": "They Rend-to-own." + }, + { + "Question": "What is Arthas' favorite camping snack?", + "Answer": "Frostsmores" + }, + { + "Question": "Why are warlocks faster after drinking vodka?", + "Answer": "Because it gives them a Burning Rush!" + }, + { + "Question": "How did the druid catch a fish?", + "Answer": "With his bear hands" + }, + { + "Question": "I really want the new Grove Warden moose mount...", + "Answer": "...you could say, I consider it a moost-have." + }, + { + "Question": "Heard about the time Millhouse Manastorm went to Blackrock Foundry to visit Oregorger?", + "Answer": "Me neither, but I've been assured there will be gnome ore puns." + }, + { + "Question": "So my new maid is a rogue...", + "Answer": "... And she keeps on using [Vanish](http://www.brockaghltd.com/wp/wp-content/uploads/2014/11/31900_4.1405683094.jpg) even when I told her I prefer [Resolve](http://ecx.images-amazon.com/images/I/81a1jTSQzbL._SY355_.jpg). I'm so fed up with her!" + }, + { + "Question": "What do fire elementals eat for breakfast?", + "Answer": "Ragnar-Os" + }, + { + "Question": "Why do mages share cane's?", + "Answer": "Because it's not his cane, it's R Cane!" + }, + { + "Question": "Why do sneaky rogues prefer leather armor?", + "Answer": "because it's made of hide" + }, + { + "Question": "What's it called when an animal spirit crashes your UI?", + "Answer": "A LOA error." + }, + { + "Question": "Man the 90s take so long", + "Answer": "" + }, + { + "Question": "What is Shadow-Lord Iskar's problem?", + "Answer": "Every time we get to him in Hellfire Citadel, he always seems to be in a Fowl mood." + }, + { + "Question": "Why are hunters bad at photography?", + "Answer": "They have no focus." + }, + { + "Question": "Why are paladins good at photography?", + "Answer": "They use the Light" + }, + { + "Question": "How did the paladins get clean?", + "Answer": "They had a bubble bath!" + }, + { + "Question": "You need to be careful of the trees in this game.", + "Answer": "Some of them are shady." + } +] \ No newline at end of file diff --git a/WizBot/packages.config b/WizBot/packages.config new file mode 100644 index 000000000..be70b6587 --- /dev/null +++ b/WizBot/packages.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NadekoBot/resources/images/cards/10_of_clubs.jpg b/WizBot/resources/images/cards/10_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/10_of_clubs.jpg rename to WizBot/resources/images/cards/10_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/10_of_diamonds.jpg b/WizBot/resources/images/cards/10_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/10_of_diamonds.jpg rename to WizBot/resources/images/cards/10_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/10_of_hearts.jpg b/WizBot/resources/images/cards/10_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/10_of_hearts.jpg rename to WizBot/resources/images/cards/10_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/10_of_spades.jpg b/WizBot/resources/images/cards/10_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/10_of_spades.jpg rename to WizBot/resources/images/cards/10_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/2_of_clubs.jpg b/WizBot/resources/images/cards/2_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/2_of_clubs.jpg rename to WizBot/resources/images/cards/2_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/2_of_diamonds.jpg b/WizBot/resources/images/cards/2_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/2_of_diamonds.jpg rename to WizBot/resources/images/cards/2_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/2_of_hearts.jpg b/WizBot/resources/images/cards/2_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/2_of_hearts.jpg rename to WizBot/resources/images/cards/2_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/2_of_spades.jpg b/WizBot/resources/images/cards/2_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/2_of_spades.jpg rename to WizBot/resources/images/cards/2_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/3_of_clubs.jpg b/WizBot/resources/images/cards/3_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/3_of_clubs.jpg rename to WizBot/resources/images/cards/3_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/3_of_diamonds.jpg b/WizBot/resources/images/cards/3_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/3_of_diamonds.jpg rename to WizBot/resources/images/cards/3_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/3_of_hearts.jpg b/WizBot/resources/images/cards/3_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/3_of_hearts.jpg rename to WizBot/resources/images/cards/3_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/3_of_spades.jpg b/WizBot/resources/images/cards/3_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/3_of_spades.jpg rename to WizBot/resources/images/cards/3_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/4_of_clubs.jpg b/WizBot/resources/images/cards/4_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/4_of_clubs.jpg rename to WizBot/resources/images/cards/4_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/4_of_diamonds.jpg b/WizBot/resources/images/cards/4_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/4_of_diamonds.jpg rename to WizBot/resources/images/cards/4_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/4_of_hearts.jpg b/WizBot/resources/images/cards/4_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/4_of_hearts.jpg rename to WizBot/resources/images/cards/4_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/4_of_spades.jpg b/WizBot/resources/images/cards/4_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/4_of_spades.jpg rename to WizBot/resources/images/cards/4_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/5_of_clubs.jpg b/WizBot/resources/images/cards/5_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/5_of_clubs.jpg rename to WizBot/resources/images/cards/5_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/5_of_diamonds.jpg b/WizBot/resources/images/cards/5_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/5_of_diamonds.jpg rename to WizBot/resources/images/cards/5_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/5_of_hearts.jpg b/WizBot/resources/images/cards/5_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/5_of_hearts.jpg rename to WizBot/resources/images/cards/5_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/5_of_spades.jpg b/WizBot/resources/images/cards/5_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/5_of_spades.jpg rename to WizBot/resources/images/cards/5_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/6_of_clubs.jpg b/WizBot/resources/images/cards/6_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/6_of_clubs.jpg rename to WizBot/resources/images/cards/6_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/6_of_diamonds.jpg b/WizBot/resources/images/cards/6_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/6_of_diamonds.jpg rename to WizBot/resources/images/cards/6_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/6_of_hearts.jpg b/WizBot/resources/images/cards/6_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/6_of_hearts.jpg rename to WizBot/resources/images/cards/6_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/6_of_spades.jpg b/WizBot/resources/images/cards/6_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/6_of_spades.jpg rename to WizBot/resources/images/cards/6_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/7_of_clubs.jpg b/WizBot/resources/images/cards/7_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/7_of_clubs.jpg rename to WizBot/resources/images/cards/7_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/7_of_diamonds.jpg b/WizBot/resources/images/cards/7_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/7_of_diamonds.jpg rename to WizBot/resources/images/cards/7_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/7_of_hearts.jpg b/WizBot/resources/images/cards/7_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/7_of_hearts.jpg rename to WizBot/resources/images/cards/7_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/7_of_spades.jpg b/WizBot/resources/images/cards/7_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/7_of_spades.jpg rename to WizBot/resources/images/cards/7_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/8_of_clubs.jpg b/WizBot/resources/images/cards/8_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/8_of_clubs.jpg rename to WizBot/resources/images/cards/8_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/8_of_diamonds.jpg b/WizBot/resources/images/cards/8_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/8_of_diamonds.jpg rename to WizBot/resources/images/cards/8_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/8_of_hearts.jpg b/WizBot/resources/images/cards/8_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/8_of_hearts.jpg rename to WizBot/resources/images/cards/8_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/8_of_spades.jpg b/WizBot/resources/images/cards/8_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/8_of_spades.jpg rename to WizBot/resources/images/cards/8_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/9_of_clubs.jpg b/WizBot/resources/images/cards/9_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/9_of_clubs.jpg rename to WizBot/resources/images/cards/9_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/9_of_diamonds.jpg b/WizBot/resources/images/cards/9_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/9_of_diamonds.jpg rename to WizBot/resources/images/cards/9_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/9_of_hearts.jpg b/WizBot/resources/images/cards/9_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/9_of_hearts.jpg rename to WizBot/resources/images/cards/9_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/9_of_spades.jpg b/WizBot/resources/images/cards/9_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/9_of_spades.jpg rename to WizBot/resources/images/cards/9_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/ace_of_clubs.jpg b/WizBot/resources/images/cards/ace_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/ace_of_clubs.jpg rename to WizBot/resources/images/cards/ace_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/ace_of_diamonds.jpg b/WizBot/resources/images/cards/ace_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/ace_of_diamonds.jpg rename to WizBot/resources/images/cards/ace_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/ace_of_hearts.jpg b/WizBot/resources/images/cards/ace_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/ace_of_hearts.jpg rename to WizBot/resources/images/cards/ace_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/ace_of_spades.jpg b/WizBot/resources/images/cards/ace_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/ace_of_spades.jpg rename to WizBot/resources/images/cards/ace_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/black_joker.jpg b/WizBot/resources/images/cards/black_joker.jpg similarity index 100% rename from NadekoBot/resources/images/cards/black_joker.jpg rename to WizBot/resources/images/cards/black_joker.jpg diff --git a/NadekoBot/resources/images/cards/jack_of_clubs.jpg b/WizBot/resources/images/cards/jack_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/jack_of_clubs.jpg rename to WizBot/resources/images/cards/jack_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/jack_of_diamonds.jpg b/WizBot/resources/images/cards/jack_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/jack_of_diamonds.jpg rename to WizBot/resources/images/cards/jack_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/jack_of_hearts.jpg b/WizBot/resources/images/cards/jack_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/jack_of_hearts.jpg rename to WizBot/resources/images/cards/jack_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/jack_of_spades.jpg b/WizBot/resources/images/cards/jack_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/jack_of_spades.jpg rename to WizBot/resources/images/cards/jack_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/king_of_clubs.jpg b/WizBot/resources/images/cards/king_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/king_of_clubs.jpg rename to WizBot/resources/images/cards/king_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/king_of_diamonds.jpg b/WizBot/resources/images/cards/king_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/king_of_diamonds.jpg rename to WizBot/resources/images/cards/king_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/king_of_hearts.jpg b/WizBot/resources/images/cards/king_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/king_of_hearts.jpg rename to WizBot/resources/images/cards/king_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/king_of_spades.jpg b/WizBot/resources/images/cards/king_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/king_of_spades.jpg rename to WizBot/resources/images/cards/king_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/queen_of_clubs.jpg b/WizBot/resources/images/cards/queen_of_clubs.jpg similarity index 100% rename from NadekoBot/resources/images/cards/queen_of_clubs.jpg rename to WizBot/resources/images/cards/queen_of_clubs.jpg diff --git a/NadekoBot/resources/images/cards/queen_of_diamonds.jpg b/WizBot/resources/images/cards/queen_of_diamonds.jpg similarity index 100% rename from NadekoBot/resources/images/cards/queen_of_diamonds.jpg rename to WizBot/resources/images/cards/queen_of_diamonds.jpg diff --git a/NadekoBot/resources/images/cards/queen_of_hearts.jpg b/WizBot/resources/images/cards/queen_of_hearts.jpg similarity index 100% rename from NadekoBot/resources/images/cards/queen_of_hearts.jpg rename to WizBot/resources/images/cards/queen_of_hearts.jpg diff --git a/NadekoBot/resources/images/cards/queen_of_spades.jpg b/WizBot/resources/images/cards/queen_of_spades.jpg similarity index 100% rename from NadekoBot/resources/images/cards/queen_of_spades.jpg rename to WizBot/resources/images/cards/queen_of_spades.jpg diff --git a/NadekoBot/resources/images/cards/red_joker.jpg b/WizBot/resources/images/cards/red_joker.jpg similarity index 100% rename from NadekoBot/resources/images/cards/red_joker.jpg rename to WizBot/resources/images/cards/red_joker.jpg diff --git a/NadekoBot/resources/images/coins/heads.png b/WizBot/resources/images/coins/heads.png similarity index 100% rename from NadekoBot/resources/images/coins/heads.png rename to WizBot/resources/images/coins/heads.png diff --git a/NadekoBot/resources/images/coins/tails.png b/WizBot/resources/images/coins/tails.png similarity index 100% rename from NadekoBot/resources/images/coins/tails.png rename to WizBot/resources/images/coins/tails.png diff --git a/NadekoBot/resources/images/dice/0.png b/WizBot/resources/images/dice/0.png similarity index 100% rename from NadekoBot/resources/images/dice/0.png rename to WizBot/resources/images/dice/0.png diff --git a/NadekoBot/resources/images/dice/1.png b/WizBot/resources/images/dice/1.png similarity index 100% rename from NadekoBot/resources/images/dice/1.png rename to WizBot/resources/images/dice/1.png diff --git a/NadekoBot/resources/images/dice/2.png b/WizBot/resources/images/dice/2.png similarity index 100% rename from NadekoBot/resources/images/dice/2.png rename to WizBot/resources/images/dice/2.png diff --git a/NadekoBot/resources/images/dice/3.png b/WizBot/resources/images/dice/3.png similarity index 100% rename from NadekoBot/resources/images/dice/3.png rename to WizBot/resources/images/dice/3.png diff --git a/NadekoBot/resources/images/dice/4.png b/WizBot/resources/images/dice/4.png similarity index 100% rename from NadekoBot/resources/images/dice/4.png rename to WizBot/resources/images/dice/4.png diff --git a/NadekoBot/resources/images/dice/5.png b/WizBot/resources/images/dice/5.png similarity index 100% rename from NadekoBot/resources/images/dice/5.png rename to WizBot/resources/images/dice/5.png diff --git a/NadekoBot/resources/images/dice/6.png b/WizBot/resources/images/dice/6.png similarity index 100% rename from NadekoBot/resources/images/dice/6.png rename to WizBot/resources/images/dice/6.png diff --git a/NadekoBot/resources/images/dice/7.png b/WizBot/resources/images/dice/7.png similarity index 100% rename from NadekoBot/resources/images/dice/7.png rename to WizBot/resources/images/dice/7.png diff --git a/NadekoBot/resources/images/dice/8.png b/WizBot/resources/images/dice/8.png similarity index 100% rename from NadekoBot/resources/images/dice/8.png rename to WizBot/resources/images/dice/8.png diff --git a/NadekoBot/resources/images/dice/9.png b/WizBot/resources/images/dice/9.png similarity index 100% rename from NadekoBot/resources/images/dice/9.png rename to WizBot/resources/images/dice/9.png diff --git a/NadekoBot/resources/images/hidden.png b/WizBot/resources/images/hidden.png similarity index 100% rename from NadekoBot/resources/images/hidden.png rename to WizBot/resources/images/hidden.png diff --git a/NadekoBot/resources/images/rip.png b/WizBot/resources/images/rip.png similarity index 100% rename from NadekoBot/resources/images/rip.png rename to WizBot/resources/images/rip.png diff --git a/commandlist.md b/commandlist.md index 6e598e1f7..58d76a471 100644 --- a/commandlist.md +++ b/commandlist.md @@ -1,27 +1,29 @@ -######For more information and how to setup your own NadekoBot, go to: **http://github.com/Kwoth/NadekoBot/** -######You can donate on paypal: `nadekodiscordbot@gmail.com` or Bitcoin `17MZz1JAqME39akMLrVT4XBPffQJ2n1EPa` +######For more information on WizBot, go to: **http://wizkiller95network.com/** -#NadekoBot List Of Commands -Version: `NadekoBot v0.9.5957.32647` +#WizBot List Of Commands +Version: `WizBot v0.9.5989.40535` ### Administration Command and aliases | Description | Usage ----------------|--------------|------- -`.greet` | Enables or Disables anouncements on the current channel when someone joins the server. -`.greetmsg` | Sets a new announce message. Type %user% if you want to mention the new member. | .greetmsg Welcome to the server, %user%. -`.bye` | Enables or Disables anouncements on the current channel when someone leaves the server. -`.byemsg` | Sets a new announce leave message. Type %user% if you want to mention the new member. | .byemsg %user% has left the server. +`.grdel` | Toggles automatic deletion of greet and bye messages. +`.greet` | Toggles anouncements on the current channel when someone joins the server. +`.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. | .greetmsg Welcome to the server, %user%. +`.bye` | Toggles anouncements on the current channel when someone leaves the server. +`.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. | .byemsg %user% has left the server. `.byepm` | Toggles whether the good bye messages will be sent in a PM or in the text channel. `.greetpm` | Toggles whether the greet messages will be sent in a PM or in the text channel. `.spmom` | Toggles whether mentions of other offline users on your server will send a pm to them. `.logserver` | Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Owner Only!** `.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. **Owner Only!** `.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. **Owner Only!** -`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. Requires manage messages. +`.repeatinvoke`, `.repinv` | Immediately shows the repeat message and restarts the timer. +`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. Requires manage messages. | `.repeat 5 Hello there` `.rotateplaying`, `.ropl` | Toggles rotation of playing status of the dynamic strings you specified earlier. `.addplaying`, `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %trivia% `.listplaying`, `.lipl` | Lists all playing statuses with their corresponding number. `.removeplaying`, `.repl`, `.rmpl` | Removes a playing string on a given number. `.slowmode` | Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. +`.cleanv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk.** `.v+t`, `.voice+text` | 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. `.scsc` | Starts an instance of cross server channel. You will get a token as a DMthat other people will use to tune in to the same instance `.jcsc` | Joins current channel to an instance of cross server channel using the token. @@ -32,13 +34,21 @@ Command and aliases | Description | Usage `.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | .iam Gamer `.iamn`, `.iamnot` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | .iamn Gamer `.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. **Owner only!** `.sinfo`, `.serverinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | .sinfo Some Server `.cinfo`, `.channelinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | .cinfo #some-channel `.uinfo`, `.userinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | .uinfo @SomeUser +`.addcustomreaction`, `.acr` | Add a custom reaction. Guide here: **Owner Only!** | .acr "hello" I love saying hello to %user% +`.listcustomreactions`, `.lcr` | Lists all current custom reactions (paginated with 5 commands per page). | .lcr 1 +`.deletecustomreaction`, `.dcr` | Deletes a custom reaction with given name (and index) +`.aar`, `.autoassignrole` | Automaticaly assigns a specified role to every user who joins the server. Type `.aar` to disable, `.aar Role Name` to enable +`.restart` | Restarts the bot. Might not work. `.sr`, `.setrole` | Sets a role for a given user. | .sr @User Guest `.rr`, `.removerole` | Removes a role from a given user. | .rr @User Admin -`.r`, `.role`, `.cr` | Creates a role with a given name. | .r Awesome Role -`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | .color Admin 255 200 100 or .color Admin ffba55 +`.renr`, `.renamerole` | Renames a role. Role you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole` +`.rar`, `.removeallroles` | Removes all roles from a mentioned user. | .rar @User +`.r`, `.role`, `.cr` | Creates a role with a given name. | `.r Awesome Role` +`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.color Admin 255 200 100` or `.color Admin ffba55` `.roles` | List all roles on this server or a single user if specified. `.b`, `.ban` | Bans a user by id or name with an optional message. | .b "@some Guy" Your behaviour is toxic. `.k`, `.kick` | Kicks a mentioned user. @@ -50,32 +60,34 @@ Command and aliases | Description | Usage `.vch`, `.cvch` | Creates a new voice channel with a given name. `.rch`, `.rtch` | Removes a text channel with a given name. `.ch`, `.tch` | Creates a new text channel with a given name. -`.st`, `.settopic`, `.topic` | Sets a topic on the current channel. +`.st`, `.settopic`, `.topic` | Sets a topic on the current channel. | `.st My new topic` +`.schn`, `.setchannelname`, `.topic` | Changed the name of the current channel. `.uid`, `.userid` | Shows user ID. `.cid`, `.channelid` | Shows current channel ID. `.sid`, `.serverid` | Shows current server ID. -`.stats` | Shows some basic stats for Nadeko. -`.dysyd` | Shows some basic stats for Nadeko. +`.stats` | Shows some basic stats for WizBot. +`.dysyd` | Shows some basic stats for WizBot. `.heap` | Shows allocated memory - **Owner Only!** -`.prune` | Prunes a number of messages from the current channel. | .prune 5 +`.prune`, `.clr` | `.prune` removes all WizBot'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`, `.graceful` | Shuts the bot down and notifies users about the restart. **Owner Only!** -`.clr` | Clears some of Nadeko's messages from the current channel. If given a user, will clear the user's messages from the current channel (**Owner Only!**) | .clr @X `.newname`, `.setname` | Give the bot a new name. **Owner Only!** -`.newavatar`, `.setavatar` | Sets a new avatar image for the NadekoBot. **Owner Only!** +`.newavatar`, `.setavatar` | Sets a new avatar image for the WizBot. **Owner Only!** `.setgame` | Sets the bots game. **Owner Only!** `.checkmyperms` | Checks your userspecific permissions on this channel. `.commsuser` | Sets a user for through-bot communication. Only works if server is set. Resets commschannel. **Owner Only!** `.commsserver` | Sets a server for through-bot communication. **Owner Only!** `.commschannel` | Sets a channel for through-bot communication. Only works if server is set. Resets commsuser. **Owner Only!** -`.send` | Send a message to someone on a different server through the bot. **Owner Only!** - | .send Message text multi word! +`.send` | Send a message to someone on a different server through the bot. **Owner Only!** | .send Message text multi word! `.menrole`, `.mentionrole` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. +`.inrole` | Lists every person from the provided role or roles (separated by a ',') on this server. `.parsetosql` | Loads exported parsedata from /data/parsedata/ into sqlite database. `.unstuck` | Clears the message queue. **Owner Only!** `.donators` | List of lovely people who donated to keep this project alive. `.adddon`, `.donadd` | Add a donator to the database. `.videocall` | Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. `.announce` | Sends a message to all servers' general channel bot is connected to.**Owner Only!** | .announce Useless spam +`.unotes` | Gives a list of updates that have been done to WizBot. +`.whoplays` | Shows a list of users who are playing the specified game. ### Help Command and aliases | Description | Usage @@ -92,12 +104,15 @@ Command and aliases | Description | Usage ----------------|--------------|------- `;cfi`, `;channelfilterinvites` | 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 `;sfi`, `;serverfilterinvites` | Enables or disables automatic deleting of invites on the server. | ;sfi disable -`;cfw`, `;channelfilterwords` | 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. | ;cfi enable #general-chat -`;afw`, `;addfilteredword` | Adds a new word to the list of filtered words | ;aw poop +`;cfw`, `;channelfilterwords` | 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 +`;afw`, `;addfilteredword` | Adds a new word to the list of filtered words | ;afw poop `;rfw`, `;removefilteredword` | Removes the word from the list of filtered words | ;rw poop `;lfw`, `;listfilteredwords` | Shows a list of filtered words | ;lfw -`;sfw`, `;serverfilterwords` | Enables or disables automatic deleting of messages containing forbidden words on the server. | ;sfi disable -`;permrole`, `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. +`;sfw`, `;serverfilterwords` | 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 'WizBot'. +`;rpc`, `;rolepermissionscopy` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;rpc Some Role ~ Some other role` +`;cpc`, `;channelpermissionscopy` | Copies BOT PERMISSIONS (not discord permissions) from one channel to another. | `;cpc Some Channel ~ Some other channel` +`;upc`, `;userpermissionscopy` | 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 `;serverperms`, `;sp` | Shows banned permissions for this server. `;roleperms`, `;rp` | Shows banned permissions for a certain role. No argument means for everyone. | ;rp AwesomeRole @@ -119,45 +134,31 @@ Command and aliases | Description | Usage `;arc`, `;allrolecommands` | Sets permissions for all commands from a certain module at the role level. | ;arc [module_name] [enable/disable] [role_name] `;ubl` | Blacklists a mentioned user. | ;ubl [user_mention] `;uubl` | Unblacklists a mentioned user. | ;uubl [user_mention] -`;cbl` | Blacklists a mentioned channel (#general for example). | ;ubl [channel_mention] +`;cbl` | Blacklists a mentioned channel (#general for example). | ;cbl [channel_mention] `;cubl` | Unblacklists a mentioned channel (#general for example). | ;cubl [channel_mention] -`;sbl` | Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | ;usl [servername/serverid] +`;sbl` | Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | ;sbl [servername/serverid] ### Conversations Command and aliases | Description | Usage ----------------|--------------|------- -`e` | You did it. Or someone else! -`comeatmebro` | Come at me bro (ง’̀-‘́)ง | comeatmebro {target} -`\o\` | Nadeko replies with /o/ -`/o/` | Nadeko replies with \o\ -`moveto` | Suggests moving the conversation. | moveto #spam `..` | 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 -`@BotName copyme`, `@BotName cm` | Nadeko starts copying everything you say. Disable with cs -`@BotName cs`, `@BotName copystop` | Nadeko stops copying you -`@BotName req`, `@BotName request` | Requests a feature for nadeko. | @NadekoBot req new_feature -`@BotName lr` | PMs the user all current nadeko requests. -`@BotName dr` | Deletes a request. Only owner is able to do this. -`@BotName rr` | Resolves a request. Only owner is able to do this. -`@BotName uptime` | Shows how long Nadeko has been running for. +`..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 copyme`, `@BotName cm` | WizBot starts copying everything you say. Disable with cs +`@BotName cs`, `@BotName copystop` | WizBot stops copying you +`@BotName req`, `@BotName request` | Requests a feature for WizBot. | @Wiz-Bot req new_feature +`@BotName lr` | PMs the user all current WizBot requests. +`@BotName dr` | Deletes a request. **Owner Only!** +`@BotName rr` | Resolves a request. **Owner Only!** +`@BotName uptime` | Shows how long WizBot has been running for. `@BotName die` | Works only for the owner. Shuts the bot down. `@BotName do you love me` | Replies with positive answer only to the bot owner. `@BotName how are you`, `@BotName how are you?` | Replies positive only if bot owner is online. -`@BotName insult` | Insults @X person. | @NadekoBot insult @X. -`@BotName praise` | Praises @X person. | @NadekoBot praise @X. -`@BotName pat` | Pat someone ^_^ -`@BotName cry` | Tell Nadeko to cry. You are a heartless monster if you use this command. -`@BotName disguise` | Tell Nadeko to disguise herself. -`@BotName are you real` | Useless. -`@BotName are you there`, `@BotName !`, `@BotName ?` | Checks if Nadeko is operational. -`@BotName draw` | Nadeko instructs you to type $draw. Gambling functions start with $ -`@BotName fire` | Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire. | @NadekoBot fire [x] -`@BotName rip` | Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000 +`@BotName fire` | Shows a unicode fire message. Optional parameter [x] tells her how many times to repeat the fire. | @Wiz-Bot fire [x] +`@BotName rip` | Shows a grave image of someone with a start year | @Wiz-Bot rip @Someone 2000 `@BotName slm` | Shows the message where you were last mentioned in this channel (checks last 10k messages) -`@BotName bb` | Says bye to someone. | @NadekoBot bb @X -`@BotName call` | Useless. Writes calling @X to chat. | @NadekoBot call @X -`@BotName hide` | Hides Nadeko in plain sight!11!! -`@BotName unhide` | Unhides Nadeko in plain sight!1!!1 +`@BotName hide` | Hides WizBot in plain sight!11!! +`@BotName unhide` | Unhides WizBot in plain sight!1!!1 `@BotName dump` | Dumps all of the invites it can to dump.txt.** Owner Only.** `@BotName ab` | Try to get 'abalabahaha' `@BotName av`, `@BotName avatar` | Shows a mentioned person's avatar. | ~av @X @@ -171,15 +172,16 @@ Command and aliases | Description | Usage `$roll` | Rolls 0-100. If you supply a number [x] it rolls up to 30 normal dice. If you split 2 numbers with letter d (xdy) it will roll x dice from 1 to y. | $roll or $roll 7 or $roll 3d5 `$nroll` | Rolls in a given range. | `$nroll 5` (rolls 0-5) or `$nroll 5-15` `$raffle` | Prints a name and ID of a random user from the online list from the (optional) role. -`$$$` | Check how much NadekoFlowers you have. -`$give` | Give someone a certain amount of NadekoFlowers +`$$$` | Check how much WizFlowers a person has. (Defaults to yourself) | `$$$` or `$$$ @Someone` +`$give` | Give someone a certain amount of WizFlowers `$award` | Gives someone a certain amount of flowers. **Owner only!** `$take` | Takes a certain amount of flowers from someone. **Owner only!** +`$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. 30 seconds per question. | `>t nohint` +`>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. `>tq` | Quits current trivia after current question. `>typestart` | Starts a typing contest. @@ -192,13 +194,13 @@ Command and aliases | Description | Usage `>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. -`>rps` | Play a game of rocket paperclip scissors with Nadeko. | >rps scissors -`>linux` | Prints a customizable Linux interjection +`>rps` | Play a game of rocket paperclip scissors with WizBot. | >rps scissors +`>linux` | Prints a customizable Linux interjection | `>linux Spyware Windows` ### Music Command and aliases | Description | Usage ----------------|--------------|------- -`!m n`, `!m next`, `!m skip` | Goes to the next song in the queue. | `!m n` +`!m n`, `!m next`, `!m skip` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. | `!m n` `!m s`, `!m stop` | Stops the music and clears the playlist. Stays in the channel. | `!m s` `!m d`, `!m destroy` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `!m d` `!m p`, `!m pause` | Pauses or Unpauses the song. | `!m p` @@ -211,10 +213,10 @@ Command and aliases | Description | Usage `!m max` | Sets the music volume to 100% (real max is actually 150%). | `!m max` `!m half` | Sets the music volume to 50%. | `!m half` `!m sh` | Shuffles the current playlist. | `!m sh` -`!m pl` | Queues up to 25 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name` -`!m lopl` | Queues up to 50 songs from a directory. **Owner Only!** | `!m lopl C:/music/classical` +`!m pl` | Queues up to 50 songs from a youtube playlist specified by a link, or keywords. | `!m pl playlist link or name` +`!m lopl` | Queues all songs from a directory. **Owner Only!** | `!m lopl C:/music/classical` `!m radio`, `!m ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf | `!m ra radio link here` -`!m lo` | Queues a local file by specifying a full path. **Owner Only!** | `!m ra C:/music/mysong.mp3` +`!m lo` | Queues a local file by specifying a full path. **Owner Only!** | `!m lo C:/music/mysong.mp3` `!m mv` | Moves the bot to your voice channel. (works only if music is already playing) | `!m mv` `!m rm` | Remove a song by its # in the queue, or 'all' to remove whole queue. | `!m rm 5` `!m cleanup` | Cleans up hanging voice connections. **Owner Only!** | `!m cleanup` @@ -222,7 +224,9 @@ Command and aliases | Description | Usage `!m rpl`, `!m repeatplaylist` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `!m rpl` `!m save` | Saves a playlist under a certain name. Name must be no longer than 20 characters and mustn't contain dashes. | `!m save classical1` `!m load` | Loads a playlist under a certain name. | `!m load classical-1` +`!m playlists`, `!m pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!m pls 1` `!m goto` | Goes to a specific time in seconds in a song. +`!m getlink`, `!m gl` | Shows a link to the currently playing song. ### Searches Command and aliases | Description | Usage @@ -236,12 +240,13 @@ Command and aliases | Description | Usage `~liststreams`, `~ls` | Lists all streams you are following on this server. | ~ls `~convert` | Convert quantities from>to. Like `~convert m>km 1000` `~convertlist` | List of the convertable dimensions and currencies. -`~we` | Shows weather data for a specified city and a country BOTH ARE REQUIRED. Weather api is very random if you make a mistake. +`~wowjoke` | Get one of Kwoth's penultimate WoW jokes. +`~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 `~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. `~imdb` | Queries imdb for movies or series, show first result. `~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. -`~randomcat` | Shows a random cat image. +`~randomcat`, `~meow` | Shows a random cat image. `~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | ~i cute kitten `~ir` | Pulls a random image using a search parameter. | ~ir cute kitten `~lmgtfy` | Google something for an idiot. @@ -256,6 +261,10 @@ Command and aliases | Description | Usage `~chucknorris`, `~cn` | Shows a random chucknorris joke from `~mi`, `~magicitem` | Shows a random magicitem from `~revav` | Returns a google reverse image search for someone's avatar. +`~revimg` | Returns a google reverse image search for an image from a 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 +`~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` ### NSFW Command and aliases | Description | Usage @@ -263,9 +272,8 @@ 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 -`~safebooru` | Shows a random image from safebooru with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~safebooru yuri+kissing -`~rule34` | Shows a random image from rule34.xx with a given tag. Tag is optional but preffered. (multiple tags are appended with +) | ~gelbooru 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 +`~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. `~boobs` | Real adult content. `~butts`, `~ass`, `~butt` | Real adult content. @@ -286,16 +294,36 @@ Command and aliases | Description | Usage ----------------|--------------|------- `>attack` | Attacks a target with the given move `>ml`, `movelist` | Lists the moves you are able to use -`>heal` | Heals someone. Revives those that fainted. Costs a NadekoFlower | >revive @someone +`>heal` | Heals someone. Revives those that fainted. Costs a WizFlower | >revive @someone `>type` | Get the poketype of the target. | >type @someone -`>settype` | Set your poketype. Costs a NadekoFlower. | >settype fire +`>settype` | Set your poketype. Costs a WizFlower. | >settype fire ### Translator Command and aliases | Description | Usage ----------------|--------------|------- -`~trans` | Translates from>to text. From the given language to the destiation language. +`~trans`, `~translate` | Translates from>to text. From the given language to the destiation language. | ~trans en>fr Hello `~translangs` | List the valid languages for translation. +### 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`, `<@!119777021319577610> insult` | Custom reaction. | %mention% insult +`@BotName praise`, `<@!119777021319577610> praise` | Custom reaction. | %mention% praise +`@BotName pat`, `<@!119777021319577610> pat` | Custom reaction. | %mention% pat +`@BotName cry`, `<@!119777021319577610> cry` | Custom reaction. | %mention% cry +`@BotName are you real?`, `<@!119777021319577610> are you real?` | Custom reaction. | %mention% are you real? +`@BotName are you there?`, `<@!119777021319577610> are you there?` | Custom reaction. | %mention% are you there? +`@BotName draw`, `<@!119777021319577610> draw` | Custom reaction. | %mention% draw +`@BotName bb`, `<@!119777021319577610> bb` | Custom reaction. | %mention% bb +`@BotName call`, `<@!119777021319577610> call` | Custom reaction. | %mention% call +`@BotName disguise`, `<@!119777021319577610> disguise` | Custom reaction. | %mention% disguise +`@BotName neko` | Shows a random picture of a neko girl. | %mention% neko + ### Trello Command and aliases | Description | Usage ----------------|--------------|------- @@ -303,4 +331,4 @@ Command and aliases | Description | Usage `trello bind` | Bind a trello bot to a single channel. You will receive notifications from your board when something is added or edited. | bind [board_id] `trello unbind` | Unbinds a bot from the channel and board. `trello lists`, `trello list` | Lists all lists yo ;) -`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. +`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. \ No newline at end of file diff --git a/discord.net b/discord.net deleted file mode 160000 index 46e5b4448..000000000 --- a/discord.net +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 46e5b44483bc82c5d81fa9a94a88e171a242008b diff --git a/discord.net/.gitattributes b/discord.net/.gitattributes new file mode 100644 index 000000000..1ff0c4230 --- /dev/null +++ b/discord.net/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/discord.net/.gitignore b/discord.net/.gitignore new file mode 100644 index 000000000..d6c4cf780 --- /dev/null +++ b/discord.net/.gitignore @@ -0,0 +1,202 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studo 2015 cache/options directory +.vs/ + +# 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 + +*_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 +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# 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 addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# 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 + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +*.[Cc]ache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +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/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +#Custom +project.lock.json +/test/Discord.Net.Tests/config.json +/docs/_build +*.pyc diff --git a/discord.net/Discord.Net.sln b/discord.net/Discord.Net.sln new file mode 100644 index 000000000..db11e8b30 --- /dev/null +++ b/discord.net/Discord.Net.sln @@ -0,0 +1,125 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8D7989F0-66CE-4DBB-8230-D8C811E9B1D7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "netplatform", "netplatform", "{EA68EBE2-51C8-4440-9EF7-D633C90A5D35}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net", "src\Discord.Net\Discord.Net.xproj", "{ACFB060B-EC8A-4926-B293-04C01E17EE23}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Commands", "src\Discord.Net.Commands\Discord.Net.Commands.xproj", "{19793545-EF89-48F4-8100-3EBAAD0A9141}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net45", "net45", "{DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6317A2E6-8E36-4C3E-949B-3F10EC888AB9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Tests", "test\Discord.Net.Tests\Discord.Net.Tests.csproj", "{855D6B1D-847B-42DA-BE6A-23683EA89511}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1BE8AF3F-3CFD-433F-A380-D294A4F617C1}" + ProjectSection(SolutionItems) = preProject + global.json = global.json + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net", "src\Discord.Net.Net45\Discord.Net.csproj", "{8D71A857-879A-4A10-859E-5FF824ED6688}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Commands", "src\Discord.Net.Commands.Net45\Discord.Net.Commands.csproj", "{1B5603B4-6F8F-4289-B945-7BAAE523D740}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Modules", "src\Discord.Net.Modules\Discord.Net.Modules.xproj", "{01584E8A-78DA-486F-9EF9-A894E435841B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Modules", "src\Discord.Net.Modules.Net45\Discord.Net.Modules.csproj", "{3091164F-66AE-4543-A63D-167C1116241D}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Discord.Net.Audio", "src\Discord.Net.Audio\Discord.Net.Audio.xproj", "{DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Discord.Net.Shared", "src\Discord.Net.Shared\Discord.Net.Shared.shproj", "{2875DEB5-F248-4105-8EA2-5141E3DE8025}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Discord.Net.Audio", "src\Discord.Net.Audio.Net45\Discord.Net.Audio.csproj", "{7BFEF748-B934-4621-9B11-6302E3A9F6B3}" +EndProject +Global + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Discord.Net.Shared\Discord.Net.Shared.projitems*{2875deb5-f248-4105-8ea2-5141e3de8025}*SharedItemsImports = 13 + src\Discord.Net.Shared\Discord.Net.Shared.projitems*{7bfef748-b934-4621-9b11-6302e3a9f6b3}*SharedItemsImports = 4 + src\Discord.Net.Shared\Discord.Net.Shared.projitems*{1b5603b4-6f8f-4289-b945-7baae523d740}*SharedItemsImports = 4 + src\Discord.Net.Shared\Discord.Net.Shared.projitems*{3091164f-66ae-4543-a63d-167c1116241d}*SharedItemsImports = 4 + 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 + FullDebug|Any CPU = FullDebug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ACFB060B-EC8A-4926-B293-04C01E17EE23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACFB060B-EC8A-4926-B293-04C01E17EE23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACFB060B-EC8A-4926-B293-04C01E17EE23}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {ACFB060B-EC8A-4926-B293-04C01E17EE23}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {ACFB060B-EC8A-4926-B293-04C01E17EE23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACFB060B-EC8A-4926-B293-04C01E17EE23}.Release|Any CPU.Build.0 = Release|Any CPU + {19793545-EF89-48F4-8100-3EBAAD0A9141}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {19793545-EF89-48F4-8100-3EBAAD0A9141}.Debug|Any CPU.Build.0 = Debug|Any CPU + {19793545-EF89-48F4-8100-3EBAAD0A9141}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {19793545-EF89-48F4-8100-3EBAAD0A9141}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {19793545-EF89-48F4-8100-3EBAAD0A9141}.Release|Any CPU.ActiveCfg = Release|Any CPU + {19793545-EF89-48F4-8100-3EBAAD0A9141}.Release|Any CPU.Build.0 = Release|Any CPU + {855D6B1D-847B-42DA-BE6A-23683EA89511}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {855D6B1D-847B-42DA-BE6A-23683EA89511}.Debug|Any CPU.Build.0 = Debug|Any CPU + {855D6B1D-847B-42DA-BE6A-23683EA89511}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {855D6B1D-847B-42DA-BE6A-23683EA89511}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.ActiveCfg = Release|Any CPU + {855D6B1D-847B-42DA-BE6A-23683EA89511}.Release|Any CPU.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}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D71A857-879A-4A10-859E-5FF824ED6688}.Release|Any CPU.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}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740}.Release|Any CPU.Build.0 = Release|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01584E8A-78DA-486F-9EF9-A894E435841B}.Release|Any CPU.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}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3091164F-66AE-4543-A63D-167C1116241D}.Release|Any CPU.Build.0 = Release|Any CPU + {DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFF7AFE3-CA77-4109-BADE-B4B49A4F6648}.Release|Any CPU.Build.0 = Release|Any CPU + {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}.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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7} + {ACFB060B-EC8A-4926-B293-04C01E17EE23} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} + {19793545-EF89-48F4-8100-3EBAAD0A9141} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} + {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} = {8D7989F0-66CE-4DBB-8230-D8C811E9B1D7} + {855D6B1D-847B-42DA-BE6A-23683EA89511} = {6317A2E6-8E36-4C3E-949B-3F10EC888AB9} + {8D71A857-879A-4A10-859E-5FF824ED6688} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} + {1B5603B4-6F8F-4289-B945-7BAAE523D740} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} + {01584E8A-78DA-486F-9EF9-A894E435841B} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} + {3091164F-66AE-4543-A63D-167C1116241D} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} + {DFF7AFE3-CA77-4109-BADE-B4B49A4F6648} = {EA68EBE2-51C8-4440-9EF7-D633C90A5D35} + {2875DEB5-F248-4105-8EA2-5141E3DE8025} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} + {7BFEF748-B934-4621-9B11-6302E3A9F6B3} = {DF03D4E8-38F6-4FE1-BC52-E38124BE8AFD} + EndGlobalSection +EndGlobal diff --git a/discord.net/LICENSE b/discord.net/LICENSE new file mode 100644 index 000000000..ebd70cd5a --- /dev/null +++ b/discord.net/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 RogueException + +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. + diff --git a/discord.net/README.md b/discord.net/README.md new file mode 100644 index 000000000..cc0b9a25a --- /dev/null +++ b/discord.net/README.md @@ -0,0 +1,23 @@ +# Discord.Net v0.9.2 +[![Build status](https://ci.appveyor.com/api/projects/status/p0n69xhqgmoobycf/branch/master?svg=true)](https://ci.appveyor.com/project/foxbot/discord-net/branch/master) + +An unofficial .Net API Wrapper for the Discord client (http://discordapp.com). + +Check out the [documentation](http://rtd.discord.foxbot.me/en/docs-dev/index.html) or join the [Discord API Chat](https://discord.gg/0SBTUU1wZTVjAMPx). + +##### Warning: Some of the documentation is outdated. +It's current being rewritten. Until that's done, feel free to use my [DiscordBot](https://github.com/RogueException/DiscordBot) repo for reference. + +### Installation +You can download Discord.Net and its extensions from NuGet: +- [Discord.Net](https://www.nuget.org/packages/Discord.Net/) +- [Discord.Net.Commands](https://www.nuget.org/packages/Discord.Net.Commands/) +- [Discord.Net.Modules](https://www.nuget.org/packages/Discord.Net.Modules/) +- [Discord.Net.Audio](https://www.nuget.org/packages/Discord.Net.Audio/) + +### Compiling +In order to compile Discord.Net, you require at least the following: +- [Visual Studio 2015](https://www.visualstudio.com/downloads/download-visual-studio-vs) +- [Visual Studio 2015 Update 2](https://www.visualstudio.com/en-us/news/vs2015-update2-vs.aspx) +- [Visual Studio .Net Core Plugin](https://www.microsoft.com/net/core#windows) +- NuGet 3.3+ (available through Visual Studio) diff --git a/discord.net/docs/Makefile b/discord.net/docs/Makefile new file mode 100644 index 000000000..5c18ee9a1 --- /dev/null +++ b/discord.net/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DiscordNet.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DiscordNet.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/DiscordNet" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DiscordNet" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/discord.net/docs/conf.py b/discord.net/docs/conf.py new file mode 100644 index 000000000..e51df21e7 --- /dev/null +++ b/discord.net/docs/conf.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +# +# Discord.Net documentation build configuration file, created by +# sphinx-quickstart on Sun Sep 27 16:59:56 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import shlex + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +sys.path.append(os.path.abspath('exts')) +extensions = ['csharp6'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Discord.Net' +copyright = u'2015, RogueException' +author = u'RogueException' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.7' +# The full version, including alpha/beta/rc tags. +release = '0.7.1b1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +rst_prolog = """ +.. include:: /global.txt +""" + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +#html_theme = 'alabaster' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'DiscordNetdoc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', + +# Latex figure (float) alignment +#'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'DiscordNet.tex', u'Discord.Net Documentation', + u'RogueException', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'discordnet', u'Discord.Net Documentation', + [author], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'DiscordNet', u'Discord.Net Documentation', + author, 'DiscordNet', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/discord.net/docs/exts/csharp6.py b/discord.net/docs/exts/csharp6.py new file mode 100644 index 000000000..2d74618e7 --- /dev/null +++ b/discord.net/docs/exts/csharp6.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +import re + +from pygments.lexer import RegexLexer, DelegatingLexer, bygroups, include, using, this, default +from pygments.token import Punctuation, Text, Comment, Operator, Keyword, Name, String, Number, Literal, Other +from pygments.util import get_choice_opt, iteritems +from pygments import unistring as uni + +from pygments.lexers.html import XmlLexer + +class CSharp6Lexer(RegexLexer): + name = 'C#6' + aliases = ['csharp6', 'c#6'] + filenames = ['*.cs'] + mimetypes = ['text/x-csharp'] + + flags = re.MULTILINE | re.DOTALL | re.UNICODE + + cs_ident = ('@?[_' + uni.combine('Lu', 'Ll', 'Lt', 'Lm', 'Nl') + ']' + + '[' + uni.combine('Lu', 'Ll', 'Lt', 'Lm', 'Nl', 'Nd', 'Pc', + 'Cf', 'Mn', 'Mc') + ']*') + + tokens = { + 'root': [ + # method names + (r'^([ \t]*(?:' + cs_ident + r'(?:\[\])?\s+)+?)' # return type + r'(' + cs_ident + ')' # method name + r'(\s*)(\()', # signature start + bygroups(using(this), Name.Function, Text, Punctuation)), + (r'^\s*\[.*?\]', Name.Attribute), + (r'[^\S\n]+', Text), + (r'\\\n', Text), # line continuation + (r'//.*?\n', Comment.Single), + (r'/[*].*?[*]/', Comment.Multiline), + (r'\n', Text), + (r'[~!%^&*()+=|\[\]:;,.<>/?-]', Punctuation), + (r'[{}]', Punctuation), + (r'@\$?"(""|[^"])*"', String), + (r'"\$?(\\\\|\\"|[^"\n])*["\n]', String), + (r"'\\.'|'[^\\]'", String.Char), + (r"[0-9](\.[0-9]*)?([eE][+-][0-9]+)?" + r"[flFLdD]?|0[xX][0-9a-fA-F]+[Ll]?", Number), + (r'#[ \t]*(if|endif|else|elif|define|undef|' + r'line|error|warning|region|endregion|pragma)\b.*?\n', + Comment.Preproc), + (r'\b(extern)(\s+)(alias)\b', bygroups(Keyword, Text, + Keyword)), + (r'(abstract|as|async|await|base|break|case|catch|' + r'checked|const|continue|default|delegate|' + r'do|else|enum|event|explicit|extern|false|finally|' + r'fixed|for|foreach|goto|if|implicit|in|interface|' + r'internal|is|lock|new|null|operator|' + r'out|override|params|private|protected|public|readonly|' + r'ref|return|sealed|sizeof|stackalloc|static|' + r'switch|this|throw|true|try|typeof|' + r'unchecked|unsafe|virtual|var|void|while|' + r'get|set|new|partial|yield|add|remove|value|alias|ascending|' + r'descending|from|group|into|orderby|select|where|' + r'join|equals)\b', Keyword), + (r'(global)(::)', bygroups(Keyword, Punctuation)), + (r'(bool|byte|char|decimal|double|dynamic|float|int|long|object|' + r'sbyte|short|string|uint|ulong|ushort|var)\b\??', Keyword.Type), + (r'(class|struct)(\s+)', bygroups(Keyword, Text), 'class'), + (r'(namespace|using)(\s+)', bygroups(Keyword, Text), 'namespace'), + (cs_ident, Name), + ], + 'class': [ + (cs_ident, Name.Class, '#pop'), + default('#pop'), + ], + 'namespace': [ + (r'(?=\()', Text, '#pop'), # using (resource) + ('(' + cs_ident + r'|\.)+', Name.Namespace, '#pop'), + ] + } + +def setup(app): + from sphinx.highlighting import lexers + lexers['csharp6'] = CSharp6Lexer() \ No newline at end of file diff --git a/discord.net/docs/features/commands.rst b/discord.net/docs/features/commands.rst new file mode 100644 index 000000000..8abfb18a9 --- /dev/null +++ b/discord.net/docs/features/commands.rst @@ -0,0 +1,20 @@ +|stub| Commands +=============== + +The `Discord.Net.Commands`_ package DiscordBotClient extends DiscordClient with support for commands. + +.. _Discord.Net.Commands: https://www.nuget.org/packages/Discord.Net.Commands + +Example (Simple) +---------------- + +.. literalinclude:: /samples/command.cs + :language: csharp6 + :tab-width: 2 + +Example (Groups) +---------------- + +.. literalinclude:: /samples/command_group.cs + :language: csharp6 + :tab-width: 2 diff --git a/discord.net/docs/features/events.rst b/discord.net/docs/features/events.rst new file mode 100644 index 000000000..2cfe27f54 --- /dev/null +++ b/discord.net/docs/features/events.rst @@ -0,0 +1,78 @@ +Events +====== + +Usage +----- +Messages from the Discord server are exposed via events on the DiscordClient class and follow the standard EventHandler C# pattern. + +.. warning:: + Note that all synchronous code in an event handler will run on the gateway socket's thread and should be handled as quickly as possible. + Using the async-await pattern to let the thread continue immediately is recommended and is demonstrated in the examples below. + +Connection State +---------------- + +Connection Events will be raised when the Connection State of your client changes. + +.. warning:: + You should not use DiscordClient.Connected to run code when your client first connects to Discord. + If you lose connection and automatically reconnect, this code will be ran again, which may lead to unexpected behavior. + +Messages +-------- + +- MessageReceived, MessageUpdated and MessageDeleted are raised when a new message arrives, an existing one has been updated (by the user, or by Discord itself), or deleted. +- MessageAcknowledged is only triggered in client mode, and occurs when a message is read on another device logged-in with your account. + +Example of MessageReceived: + +.. code-block:: c# + + // (Preface: Echo Bots are discouraged, make sure your bot is not running in a public server if you use them) + + // Hook into the MessageReceived event using a Lambda + _client.MessageReceived += async (s, e) => { + // Check to make sure that the bot is not the author + if (!e.Message.IsAuthor) + // Echo the message back to the channel + await e.Channel.SendMessage(e.Message); + }; + +Users +----- + +There are several user events: + +- UserBanned: A user has been banned from a server. +- UserUnbanned: A user was unbanned. +- UserJoined: A user joins a server. +- UserLeft: A user left (or was kicked from) a server. +- UserIsTyping: A user in a channel starts typing. +- UserUpdated: A user object was updated (presence update, role/permission change, or a voice state update). + +.. note:: + UserUpdated Events include a ``User`` object for Before and After the change. + When accessing the User, you should only use ``e.Before`` if comparing changes, otherwise use ``e.After`` + +Examples: + +.. code-block:: c# + + // Register a Hook into the UserBanned event using a Lambda + _client.UserBanned += async (s, e) => { + // Create a Channel object by searching for a channel named '#logs' on the server the ban occurred in. + var logChannel = e.Server.FindChannels("logs").FirstOrDefault(); + // Send a message to the server's log channel, stating that a user was banned. + await logChannel.SendMessage($"User Banned: {e.User.Name}"); + }; + + // Register a Hook into the UserUpdated event using a Lambda + _client.UserUpdated += async (s, e) => { + // Check that the user is in a Voice channel + if (e.After.VoiceChannel == null) return; + + // See if they changed Voice channels + if (e.Before.VoiceChannel == e.After.VoiceChannel) return; + + await logChannel.SendMessage($"User {e.After.Name} changed voice channels!"); + }; diff --git a/discord.net/docs/features/logging.rst b/discord.net/docs/features/logging.rst new file mode 100644 index 000000000..4b9f254a5 --- /dev/null +++ b/discord.net/docs/features/logging.rst @@ -0,0 +1,35 @@ +Logging +======= + +Discord.Net will log all of its events/exceptions using a built-in LogManager. +This LogManager can be accessed through DiscordClient.Log + +Usage +----- + +To handle Log Messages through Discord.Net's Logger, you must hook into the Log.Message Event. + +The LogManager does not provide a string-based result for the message, you must put your own message format together using the data provided through LogMessageEventArgs +See the Example for a snippet of logging. + +Logging Your Own Data +--------------------- + +The LogManager included in Discord.Net can also be used to log your own messages. + +You can use DiscordClient.Log.Log(LogSeverity, Source, Message, Exception), or one of the shortcut helpers, to log data. + +Example: +.. code-block:: c# + + _client.MessageReceived += async (s, e) { + // Log a new Message with Severity Info, Sourced from 'MessageReceived', with the Message Contents. + _client.Log.Info("MessageReceived", e.Message.Text, null); + }; + +Example +------- + +.. literalinclude:: /samples/logging.cs + :language: c# + :tab-width: 2 diff --git a/discord.net/docs/features/management.rst b/discord.net/docs/features/management.rst new file mode 100644 index 000000000..accbf5d94 --- /dev/null +++ b/discord.net/docs/features/management.rst @@ -0,0 +1,4 @@ +|stub| Server Management +======================== + +|stub-desc| \ No newline at end of file diff --git a/discord.net/docs/features/permissions.rst b/discord.net/docs/features/permissions.rst new file mode 100644 index 000000000..058fe07cf --- /dev/null +++ b/discord.net/docs/features/permissions.rst @@ -0,0 +1,75 @@ +Permissions +=========== + +There are two types of permissions: *Channel Permissions* and *Server Permissions*. + +Channel Permissions +------------------- +Channel Permissions are controlled using a set of flags: + +======================= ======= ============== +Flag Type Description +======================= ======= ============== +AttachFiles Text Send files to a channel. +Connect Voice Connect to a voice channel. +CreateInstantInvite General Create an invite to the channel. +DeafenMembers Voice Prevent users of a voice channel from hearing other users (server-wide). +EmbedLinks Text Create embedded links. +ManageChannel General Manage a channel. +ManageMessages Text Remove messages in a channel. +ManagePermissions General Manage the permissions of a channel. +MentionEveryone Text Use @everyone in a channel. +MoveMembers Voice Move members around in voice channels. +MuteMembers Voice Mute users of a voice channel (server-wide). +ReadMessageHistory Text Read the chat history of a voice channel. +ReadMessages Text Read any messages in a text channel; exposes the text channel to users. +SendMessages Text Send messages in a text channel. +SendTTSMessages Text Send TTS messages in a text channel. +Speak Voice Speak in a voice channel. +UseVoiceActivation Voice Use Voice Activation in a text channel (for large channels where PTT is preferred) +======================= ======= ============== + +If a user has a permission, the value is true. Otherwise, it must be null. + +Dual Channel Permissions +------------------------ +You may also access a user's permissions in a channel with the DualChannelPermissions class. +Unlike normal ChannelPermissions, DualChannelPermissions hold three values: + +If a user has a permission, the value is true. If a user is denied a permission, it will be false. If the permission is not set, the value will return null. + +Setting Channel Permissions +--------------------------- + +To set channel permissions, you may use either two ChannelPermissions, or one DualChannelPermissions. + +In the case of using two Channel Permissions, you must create one list of allowed permissions, and one list of denied permissions. +Otherwise, you can use a single DualChannelPermissions. + +Server Permissions +------------------ + +Server Permissions can be accessed by ``Server.GetPermissions(User)``, and updated with ``Server.UpdatePermissions(User, ServerPermissions)`` + +A user's server permissions also contain the default values for it's channel permissions, so the channel permissions listed above are also valid flags for Server Permissions. There are also a few extra Server Permissions: + +======================= ======= ============== +Flag Type Description +======================= ======= ============== +BanMembers Server Ban users from the server. +KickMembers Server Kick users from the server. They can still rejoin. +ManageRoles Server Manage roles on the server, and their permissions. +ManageChannels Server Manage channels that exist on the server (add, remove them) +ManageServer Server Manage the server settings. + +Roles +----- + +Managing permissions for roles is much easier than for users in channels. For roles, just access the flag under `Role.Permissions`. + +Example +------- + +.. literalinclude:: /samples/permissions.cs + :language: csharp6 + :tab-width: 2 diff --git a/discord.net/docs/features/voice.rst b/discord.net/docs/features/voice.rst new file mode 100644 index 000000000..fc6867b58 --- /dev/null +++ b/discord.net/docs/features/voice.rst @@ -0,0 +1,13 @@ +|stub| Voice +================= + +|stub-desc| + +Broadcasting +------------ + +Multi-Server Broadcasting +------------------------- + +Receiving +--------- \ No newline at end of file diff --git a/discord.net/docs/getting_started.rst b/discord.net/docs/getting_started.rst new file mode 100644 index 000000000..f9dfd857d --- /dev/null +++ b/discord.net/docs/getting_started.rst @@ -0,0 +1,48 @@ +Getting Started +=============== + +Requirements +------------ + +Discord.Net currently requires logging in with a claimed account - anonymous logins are not supported. You can `register for a Discord account here`_. + +New accounts are also useless when not connected to a server, so you should create an invite code for whatever server you intend to test on using the official Discord client. + +.. _register for a Discord account here: https://discordapp.com/register + +Installation +------------ + +You can get Discord.Net from NuGet: + +* `Discord.Net`_ +* `Discord.Net.Commands`_ +* `Discord.Net.Modules`_ +* `Discord.Net.Audio`_ + +If you have trouble installing from NuGet, try installing dependencies manually. + +You can also pull the latest source from `GitHub`_ + +.. _Discord.Net: https://www.nuget.org/packages/Discord.Net +.. _Discord.Net.Commands: https://www.nuget.org/packages/Discord.Net.Commands +.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Modules +.. _Discord.Net.Modules: https://www.nuget.org/packages/Discord.Net.Audio +.. _GitHub: https://github.com/RogueException/Discord.Net/ + +Async +----- + +Discord.Net uses C# tasks extensively - nearly all operations return one. It is highly recommended that these tasks be awaited whenever possible. +To do so requires the calling method be marked as async, which can be problematic in a console application. An example of how to get around this is provided below. + +For more information, go to `MSDN's Await-Async section`_. + +.. _MSDN's Await-Async section: https://msdn.microsoft.com/en-us/library/hh191443.aspx + +Example +------- + +.. literalinclude:: samples/getting_started.cs + :language: csharp6 + :tab-width: 2 diff --git a/discord.net/docs/global.txt b/discord.net/docs/global.txt new file mode 100644 index 000000000..e5b572c93 --- /dev/null +++ b/discord.net/docs/global.txt @@ -0,0 +1,2 @@ +.. |stub| unicode:: U+1F527 +.. |stub-desc| replace:: This page is a placeholder and has not been written yet. It should be coming soon! \ No newline at end of file diff --git a/discord.net/docs/index.rst b/discord.net/docs/index.rst new file mode 100644 index 000000000..c4469cd46 --- /dev/null +++ b/discord.net/docs/index.rst @@ -0,0 +1,36 @@ +Discord.Net +=========== + +Discord.Net is an unofficial C# wrapper around the `Discord Chat Service`. +It offers several methods to create automated operations, bots, or even custom clients. + +Feel free to join us in the `Discord API chat`_. + +.. _Discord chat service: https://discordapp.com +.. _Discord API chat: https://discord.gg/0SBTUU1wZTVjAMPx + +.. warn:: + +This is a beta! + +This library has been built thanks to a community effort reverse engineering the Discord client. +As the API is still unofficial, it may change at any time without notice, breaking this library as well. +Discord.Net itself is still in development (and is currently undergoing a rewrite) and you may encounter breaking changes throughout development until the official Discord API is released. + +It is highly recommended that you always use the latest version and please report any bugs you find to our `Discord chat`_. + +.. _Discord chat: https://discord.gg/0SBTUU1wZTVjAMPx + +This Documentation is **currently undergoing a rewrite**. Some pages (marked with a wrench) are not updated, or are not completed yet. + +.. toctree:: + :caption: Documentation + :maxdepth: 2 + + getting_started + features/logging + features/management + features/permissions + features/commands + features/voice + features/events diff --git a/discord.net/docs/make.bat b/discord.net/docs/make.bat new file mode 100644 index 000000000..f975b634c --- /dev/null +++ b/discord.net/docs/make.bat @@ -0,0 +1,263 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + echo. coverage to run coverage check of the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +REM Check if sphinx-build is available and fallback to Python version if any +%SPHINXBUILD% 2> nul +if errorlevel 9009 goto sphinx_python +goto sphinx_ok + +:sphinx_python + +set SPHINXBUILD=python -m sphinx.__init__ +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +:sphinx_ok + + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\DiscordNet.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\DiscordNet.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %~dp0 + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "coverage" ( + %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage + if errorlevel 1 exit /b 1 + echo. + echo.Testing of coverage in the sources finished, look at the ^ +results in %BUILDDIR%/coverage/python.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/discord.net/docs/samples/command.cs b/discord.net/docs/samples/command.cs new file mode 100644 index 000000000..d32e216c3 --- /dev/null +++ b/discord.net/docs/samples/command.cs @@ -0,0 +1,10 @@ +//Since we have setup our CommandChar to be '~', we will run this command by typing ~greet +commands.CreateCommand("greet") //create command greet + .Alias(new string[] { "gr", "hi" }) //add 2 aliases, so it can be run with ~gr and ~hi + .Description("Greets a person.") //add description, it will be shown when ~help is used + .Parameter("GreetedPerson", ParameterType.Required) //as an argument, we have a person we want to greet + .Do(async e => + { + await client.SendMessage(e.Channel, e.User.Name + " greets " + e.GetArg("GreetedPerson")); + //sends a message to channel with the given text + }); \ No newline at end of file diff --git a/discord.net/docs/samples/command_group.cs b/discord.net/docs/samples/command_group.cs new file mode 100644 index 000000000..5610723be --- /dev/null +++ b/discord.net/docs/samples/command_group.cs @@ -0,0 +1,21 @@ +//we would run our commands with ~do greet X and ~do bye X +commands.CreateGroup("do", cgb => + { + cgb.CreateCommand("greet") + .Alias(new string[] { "gr", "hi" }) + .Description("Greets a person.") + .Parameter("GreetedPerson", ParameterType.Required) + .Do(async e => + { + await client.SendMessage(e.Channel, e.User.Name + " greets " + e.GetArg("GreetedPerson")); + }); + + cgb.CreateCommand("bye") + .Alias(new string[] { "bb", "gb" }) + .Description("Greets a person.") + .Parameter("GreetedPerson", ParameterType.Required) + .Do(async e => + { + await client.SendMessage(e.Channel, e.User.Name + " says goodbye to " + e.GetArg("GreetedPerson")); + }); + }); \ No newline at end of file diff --git a/discord.net/docs/samples/command_service_creation.cs b/discord.net/docs/samples/command_service_creation.cs new file mode 100644 index 000000000..013619ff5 --- /dev/null +++ b/discord.net/docs/samples/command_service_creation.cs @@ -0,0 +1,9 @@ +//create command service +var commandService = new CommandService(new CommandServiceConfig +{ + CommandChar = '~', // prefix char for commands + HelpMode = HelpMode.Public +}); + +//add command service +var commands = client.AddService(commandService); \ No newline at end of file diff --git a/discord.net/docs/samples/events.cs b/discord.net/docs/samples/events.cs new file mode 100644 index 000000000..7f68bf6cb --- /dev/null +++ b/discord.net/docs/samples/events.cs @@ -0,0 +1,27 @@ +class Program +{ + private static DiscordBotClient _client; + static void Main(string[] args) + { + var client = new DiscordClient(); + + // Handle Events using Lambdas + client.MessageCreated += (s, e) => + { + if (!e.Message.IsAuthor) + await client.SendMessage(e.Message.ChannelId, "foo"); + } + + // Handle Events using Event Handlers + EventHandler handler = new EventHandler(HandleMessageCreated); + client.MessageCreated += handler; + } + + + // NOTE: When using this method, 'client' must be accessible from outside the Main function. + static void HandleMessageCreated(object sender, EventArgs e) + { + if (!e.Message.IsAuthor) + await client.SendMessage(e.Message.ChannelId, "foo"); + } +} \ No newline at end of file diff --git a/discord.net/docs/samples/getting_started.cs b/discord.net/docs/samples/getting_started.cs new file mode 100644 index 000000000..55f7923a4 --- /dev/null +++ b/discord.net/docs/samples/getting_started.cs @@ -0,0 +1,28 @@ +class Program +{ + static void Main(string[] args) + { + var client = new DiscordClient(); + + //Display all log messages in the console + client.LogMessage += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}"); + + //Echo back any message received, provided it didn't come from the bot itself + client.MessageReceived += async (s, e) => + { + if (!e.Message.IsAuthor) + await e.Channel.SendMessage(e.Message.Text); + }; + + //Convert our sync method to an async one and block the Main function until the bot disconnects + client.ExecuteAndWait(async () => + { + //Connect to the Discord server using our email and password + await client.Connect("discordtest@email.com", "Password123"); + + //If we are not a member of any server, use our invite code (made beforehand in the official Discord Client) + if (!client.Servers.Any()) + await client.AcceptInvite(client.GetInvite("aaabbbcccdddeee")); + }); + } +} diff --git a/discord.net/docs/samples/logging.cs b/discord.net/docs/samples/logging.cs new file mode 100644 index 000000000..c68b8aded --- /dev/null +++ b/discord.net/docs/samples/logging.cs @@ -0,0 +1,20 @@ +class Program +{ + private static DiscordBotClient _client; + static void Main(string[] args) + { + var client = new DiscordClient(x => + { + LogLevel = LogSeverity.Info + }); + + _client.Log.Message += (s, e) => Console.WriteLine($"[{e.Severity}] {e.Source}: {e.Message}"); + + client.ExecuteAndWait(async () => + { + await client.Connect("discordtest@email.com", "Password123"); + if (!client.Servers.Any()) + await client.AcceptInvite("aaabbbcccdddeee"); + }); + } +} diff --git a/discord.net/docs/samples/permissions.cs b/discord.net/docs/samples/permissions.cs new file mode 100644 index 000000000..419026714 --- /dev/null +++ b/discord.net/docs/samples/permissions.cs @@ -0,0 +1,14 @@ +// Find a User's Channel Permissions +var userChannelPermissions = user.GetPermissions(channel); + +// Find a User's Server Permissions +var userServerPermissions = user.ServerPermissions(); +var userServerPermissions = server.GetPermissions(user); + +// Set a User's Channel Permissions (using DualChannelPermissions) + +var userPerms = user.GetPermissions(channel); +userPerms.ReadMessageHistory = false; +userPerms.AttachFiles = null; +channel.AddPermissionsRule(user, userPerms); +} diff --git a/discord.net/global.json b/discord.net/global.json new file mode 100644 index 000000000..17556ef1b --- /dev/null +++ b/discord.net/global.json @@ -0,0 +1,6 @@ +{ + "projects": [ "src" ], + "sdk": { + "version": "1.0.0-preview1-002702" + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj b/discord.net/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj new file mode 100644 index 000000000..f7326b4a9 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio.Net45/Discord.Net.Audio.csproj @@ -0,0 +1,128 @@ + + + + + Debug + AnyCPU + {7BFEF748-B934-4621-9B11-6302E3A9F6B3} + Library + Properties + Discord.Audio + Discord.Net.Audio + 512 + v4.5 + False + + + + true + full + false + bin\Debug\ + TRACE;DEBUG;NET45 + prompt + 4 + 6 + true + true + + + pdbonly + true + bin\Release\ + TRACE;NET45 + prompt + 4 + true + 6 + true + + + + + + + AudioClient.cs + + + AudioExtensions.cs + + + AudioMode.cs + + + AudioService.cs + + + AudioServiceConfig.cs + + + IAudioClient.cs + + + InternalFrameEventArgs.cs + + + InternalIsSpeakingEventArgs.cs + + + Net\VoiceSocket.cs + + + Opus\OpusConverter.cs + + + Opus\OpusDecoder.cs + + + Opus\OpusEncoder.cs + + + Sodium\SecretBox.cs + + + UserIsTalkingEventArgs.cs + + + VirtualClient.cs + + + VoiceBuffer.cs + + + VoiceDisconnectedEventArgs.cs + + + + + + {8d71a857-879a-4a10-859e-5ff824ed6688} + Discord.Net + + + + + libsodium.dll + Always + + + opus.dll + Always + + + + + + project.json + + + + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Audio.Net45/Properties/AssemblyInfo.cs b/discord.net/src/Discord.Net.Audio.Net45/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..49b901248 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio.Net45/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Discord.Net.Audio")] +[assembly: AssemblyDescription("A Discord.Net extension adding voice support.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("RogueException")] +[assembly: AssemblyProduct("Discord.Net.Modules")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] +[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] + +[assembly: AssemblyVersion("0.9.2.0")] +[assembly: AssemblyFileVersion("0.9.2.0")] + diff --git a/discord.net/src/Discord.Net.Audio.Net45/project.json b/discord.net/src/Discord.Net.Audio.Net45/project.json new file mode 100644 index 000000000..b776ee3d7 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio.Net45/project.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "Newtonsoft.Json": "8.0.4-beta1", + "Nito.AsyncEx": "3.0.1" + }, + "frameworks": { + "net45": {} + }, + "runtimes": { + "win": {}, + "win-x86": {}, + "win-x64": {} + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Audio/AudioClient.cs b/discord.net/src/Discord.Net.Audio/AudioClient.cs new file mode 100644 index 000000000..ed5d4fed5 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/AudioClient.cs @@ -0,0 +1,260 @@ +using Discord.API.Client.GatewaySocket; +using Discord.Logging; +using Discord.Net.Rest; +using Discord.Net.WebSockets; +using Newtonsoft.Json; +using Nito.AsyncEx; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Audio +{ + internal class AudioClient : IAudioClient + { + private class OutStream : Stream + { + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + + private readonly AudioClient _client; + + internal OutStream(AudioClient client) + { + _client = client; + } + + public override long Length { get { throw new InvalidOperationException(); } } + public override long Position + { + get { throw new InvalidOperationException(); } + set { throw new InvalidOperationException(); } + } + public override void Flush() { throw new InvalidOperationException(); } + public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); } + public override void SetLength(long value) { throw new InvalidOperationException(); } + public override int Read(byte[] buffer, int offset, int count) { throw new InvalidOperationException(); } + public override void Write(byte[] buffer, int offset, int count) + { + _client.Send(buffer, offset, count); + } + } + + private readonly DiscordConfig _config; + private readonly AsyncLock _connectionLock; + private readonly TaskManager _taskManager; + private ConnectionState _gatewayState; + + internal Logger Logger { get; } + + public int Id { get; } + public AudioService Service { get; } + public AudioServiceConfig Config { get; } + public RestClient ClientAPI { get; } + public GatewaySocket GatewaySocket { get; } + public VoiceSocket VoiceSocket { get; } + public JsonSerializer Serializer { get; } + public Stream OutputStream { get; } + + public CancellationToken CancelToken { get; private set; } + public string SessionId => GatewaySocket.SessionId; + + public ConnectionState State => VoiceSocket.State; + public Server Server => VoiceSocket.Server; + public Channel Channel => VoiceSocket.Channel; + + public AudioClient(DiscordClient client, Server server, int id) + { + Id = id; + Service = client.GetService(); + Config = Service.Config; + Serializer = client.Serializer; + _gatewayState = (int)ConnectionState.Disconnected; + + //Logging + Logger = client.Log.CreateLogger($"AudioClient #{id}"); + + //Async + _taskManager = new TaskManager(Cleanup, false); + _connectionLock = new AsyncLock(); + CancelToken = new CancellationToken(true); + + //Networking + _config = client.Config; + GatewaySocket = client.GatewaySocket; + GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); + VoiceSocket = new VoiceSocket(_config, Config, client.Serializer, client.Log.CreateLogger($"Voice #{id}")); + VoiceSocket.Server = server; + OutputStream = new OutStream(this); + } + + public async Task Connect() + { + var cancelSource = new CancellationTokenSource(); + CancelToken = cancelSource.Token; + await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false); + } + private async Task BeginGatewayConnect() + { + try + { + using (await _connectionLock.LockAsync().ConfigureAwait(false)) + { + await Disconnect().ConfigureAwait(false); + _taskManager.ClearException(); + + ClientAPI.Token = Service.Client.ClientAPI.Token; + + Stopwatch stopwatch = null; + if (_config.LogLevel >= LogSeverity.Verbose) + stopwatch = Stopwatch.StartNew(); + _gatewayState = ConnectionState.Connecting; + + var cancelSource = new CancellationTokenSource(); + CancelToken = cancelSource.Token; + ClientAPI.CancelToken = CancelToken; + + await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false); + + await _taskManager.Start(new Task[0], cancelSource).ConfigureAwait(false); + GatewaySocket.WaitForConnection(CancelToken); + + if (_config.LogLevel >= LogSeverity.Verbose) + { + stopwatch.Stop(); + double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); + Logger.Verbose($"Connection took {seconds} sec"); + } + } + } + catch (Exception ex) + { + await _taskManager.SignalError(ex).ConfigureAwait(false); + throw; + } + } + private void EndGatewayConnect() + { + _gatewayState = ConnectionState.Connected; + } + + public async Task Disconnect() + { + await _taskManager.Stop(true).ConfigureAwait(false); + } + private async Task Cleanup() + { + var oldState = _gatewayState; + _gatewayState = ConnectionState.Disconnecting; + + var server = VoiceSocket.Server; + VoiceSocket.Server = null; + VoiceSocket.Channel = null; + await Service.RemoveClient(server, this).ConfigureAwait(false); + SendVoiceUpdate(server.Id, null); + + await VoiceSocket.Disconnect().ConfigureAwait(false); + + _gatewayState = (int)ConnectionState.Disconnected; + } + + public async Task Join(Channel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (channel.Type != ChannelType.Voice) + throw new ArgumentException("Channel must be a voice channel.", nameof(channel)); + if (channel == VoiceSocket.Channel) return; + var server = channel.Server; + if (server != VoiceSocket.Server) + throw new ArgumentException("This channel is not part of the current server.", nameof(channel)); + if (VoiceSocket.Server == null) + throw new InvalidOperationException("This client has been closed."); + + SendVoiceUpdate(channel.Server.Id, channel.Id); + using (await _connectionLock.LockAsync().ConfigureAwait(false)) + await Task.Run(() => VoiceSocket.WaitForConnection(CancelToken)).ConfigureAwait(false); + } + + private async void OnReceivedEvent(WebSocketEventEventArgs e) + { + try + { + switch (e.Type) + { + case "VOICE_STATE_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + if (data.GuildId == VoiceSocket.Server?.Id && data.UserId == Service.Client.CurrentUser?.Id) + { + if (data.ChannelId == null) + await Disconnect().ConfigureAwait(false); + else + { + var channel = Service.Client.GetChannel(data.ChannelId.Value); + if (channel != null) + VoiceSocket.Channel = channel; + else + { + Logger.Warning("VOICE_STATE_UPDATE referenced an unknown channel, disconnecting."); + await Disconnect().ConfigureAwait(false); + } + } + } + } + break; + case "VOICE_SERVER_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + if (data.GuildId == VoiceSocket.Server?.Id) + { + var client = Service.Client; + var id = client.CurrentUser?.Id; + if (id != null) + { + var host = "wss://" + e.Payload.Value("endpoint").Split(':')[0]; + await VoiceSocket.Connect(host, data.Token, id.Value, GatewaySocket.SessionId, CancelToken).ConfigureAwait(false); + } + } + } + break; + } + } + catch (Exception ex) + { + Logger.Error($"Error handling {e.Type} event", ex); + } + } + + public void Send(byte[] data, int offset, int count) + { + if (data == null) throw new ArgumentException(nameof(data)); + if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); + if (offset < 0) throw new ArgumentOutOfRangeException(nameof(offset)); + if (VoiceSocket.Server == null) return; //Has been closed + if (count == 0) return; + + VoiceSocket.SendPCMFrames(data, offset, count); + } + + public void Clear() + { + if (VoiceSocket.Server == null) return; //Has been closed + VoiceSocket.ClearPCMFrames(); + } + public void Wait() + { + if (VoiceSocket.Server == null) return; //Has been closed + VoiceSocket.WaitForQueue(); + } + + public void SendVoiceUpdate(ulong? serverId, ulong? channelId) + { + GatewaySocket.SendUpdateVoice(serverId, channelId, + (Service.Config.Mode | AudioMode.Outgoing) == 0, + (Service.Config.Mode | AudioMode.Incoming) == 0); + } + } +} diff --git a/discord.net/src/Discord.Net.Audio/AudioExtensions.cs b/discord.net/src/Discord.Net.Audio/AudioExtensions.cs new file mode 100644 index 000000000..705cc893f --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/AudioExtensions.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; + +namespace Discord.Audio +{ + public static class AudioExtensions + { + public static DiscordClient UsingAudio(this DiscordClient client, AudioServiceConfig config = null) + { + client.AddService(new AudioService(config)); + return client; + } + public static DiscordClient UsingAudio(this DiscordClient client, Action configFunc = null) + { + var builder = new AudioServiceConfigBuilder(); + configFunc(builder); + client.AddService(new AudioService(builder)); + return client; + } + + public static Task JoinAudio(this Channel channel) => channel.Client.GetService().Join(channel); + public static Task LeaveAudio(this Channel channel) => channel.Client.GetService().Leave(channel); + public static Task LeaveAudio(this Server server) => server.Client.GetService().Leave(server); + public static IAudioClient GetAudioClient(this Server server) => server.Client.GetService().GetClient(server); + } +} diff --git a/discord.net/src/Discord.Net.Audio/AudioMode.cs b/discord.net/src/Discord.Net.Audio/AudioMode.cs new file mode 100644 index 000000000..b9acdbf89 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/AudioMode.cs @@ -0,0 +1,9 @@ +namespace Discord.Audio +{ + public enum AudioMode : byte + { + Outgoing = 1, + Incoming = 2, + Both = Outgoing | Incoming + } +} diff --git a/discord.net/src/Discord.Net.Audio/AudioService.cs b/discord.net/src/Discord.Net.Audio/AudioService.cs new file mode 100644 index 000000000..3584b2075 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/AudioService.cs @@ -0,0 +1,118 @@ +using Nito.AsyncEx; +using System; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace Discord.Audio +{ + public class AudioService : IService + { + private readonly AsyncLock _asyncLock; + private ConcurrentDictionary _voiceClients; + private ConcurrentDictionary _talkingUsers; + private int _nextClientId; + + public DiscordClient Client { get; private set; } + public AudioServiceConfig Config { get; } + + public event EventHandler Connected = delegate { }; + public event EventHandler Disconnected = delegate { }; + public event EventHandler UserIsSpeakingUpdated = delegate { }; + + private void OnConnected() + => Connected(this, EventArgs.Empty); + private void OnDisconnected(ulong serverId, bool wasUnexpected, Exception ex) + => Disconnected(this, new VoiceDisconnectedEventArgs(serverId, wasUnexpected, ex)); + private void OnUserIsSpeakingUpdated(User user, bool isSpeaking) + => UserIsSpeakingUpdated(this, new UserIsSpeakingEventArgs(user, isSpeaking)); + + public AudioService() + : this(new AudioServiceConfigBuilder()) + { + } + public AudioService(AudioServiceConfigBuilder builder) + : this(builder.Build()) + { + } + public AudioService(AudioServiceConfig config) + { + Config = config; + _asyncLock = new AsyncLock(); + + } + void IService.Install(DiscordClient client) + { + Client = client; + + _voiceClients = new ConcurrentDictionary(); + + _talkingUsers = new ConcurrentDictionary(); + + client.GatewaySocket.Disconnected += (s, e) => + { + foreach (var member in _talkingUsers) + { + bool ignored; + if (_talkingUsers.TryRemove(member.Key, out ignored)) + OnUserIsSpeakingUpdated(member.Key, false); + } + }; + } + + public IAudioClient GetClient(Server server) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + AudioClient client; + if (_voiceClients.TryGetValue(server.Id, out client)) + return client; + else + return null; + } + + //Called from AudioClient.Cleanup + internal async Task RemoveClient(Server server, AudioClient client) + { + using (await _asyncLock.LockAsync().ConfigureAwait(false)) + { + if (_voiceClients.TryUpdate(server.Id, null, client)) + _voiceClients.TryRemove(server.Id, out client); + } + } + + public async Task Join(Channel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + + var server = channel.Server; + using (await _asyncLock.LockAsync().ConfigureAwait(false)) + { + AudioClient client; + if (!_voiceClients.TryGetValue(server.Id, out client)) + { + client = new AudioClient(Client, server, unchecked(++_nextClientId)); + _voiceClients[server.Id] = client; + + await client.Connect().ConfigureAwait(false); + } + + await client.Join(channel).ConfigureAwait(false); + return client; + } + } + + public Task Leave(Server server) => Leave(server, null); + public Task Leave(Channel channel) => Leave(channel.Server, channel); + private async Task Leave(Server server, Channel channel) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + + AudioClient client; + //Potential race condition if changing channels during this call, but that's acceptable + if (channel == null || (_voiceClients.TryGetValue(server.Id, out client) && client.Channel == channel)) + { + if (_voiceClients.TryRemove(server.Id, out client)) + await client.Disconnect().ConfigureAwait(false); + } + } + } +} diff --git a/discord.net/src/Discord.Net.Audio/AudioServiceConfig.cs b/discord.net/src/Discord.Net.Audio/AudioServiceConfig.cs new file mode 100644 index 000000000..558d9b262 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/AudioServiceConfig.cs @@ -0,0 +1,44 @@ +namespace Discord.Audio +{ + public class AudioServiceConfigBuilder + { + /// Enables the voice websocket and UDP client and specifies how it will be used. + public AudioMode Mode { get; set; } = AudioMode.Outgoing; + + /// Enables the voice websocket and UDP client. This option requires the libsodium .dll or .so be in the local or system folder. + public bool EnableEncryption { get; set; } = true; + + /// Gets or sets the buffer length (in milliseconds) for outgoing voice packets. + public int BufferLength { get; set; } = 1000; + /// Gets or sets the bitrate used (in kbit/s, between 1 and MaxBitrate inclusively) for outgoing voice packets. A null value will use default Opus settings. + public int? Bitrate { get; set; } = null; + /// Gets or sets the number of channels (1 or 2) used in both input provided to IAudioClient and output send to Discord. Defaults to 2 (stereo). + public int Channels { get; set; } = 2; + + public AudioServiceConfig Build() => new AudioServiceConfig(this); + } + + public class AudioServiceConfig + { + public const int MaxBitrate = 128; + + public AudioMode Mode { get; } + + public bool EnableEncryption { get; } + + public int BufferLength { get; } + public int? Bitrate { get; } + public int Channels { get; } + + internal AudioServiceConfig(AudioServiceConfigBuilder builder) + { + Mode = builder.Mode; + + EnableEncryption = builder.EnableEncryption; + + BufferLength = builder.BufferLength; + Bitrate = builder.Bitrate; + Channels = builder.Channels; + } + } +} diff --git a/discord.net/src/Discord.Net.Audio/Discord.Net.Audio.xproj b/discord.net/src/Discord.Net.Audio/Discord.Net.Audio.xproj new file mode 100644 index 000000000..c5a2a40f1 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/Discord.Net.Audio.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + dff7afe3-ca77-4109-bade-b4b49a4f6648 + Discord.Audio + ..\..\artifacts\obj\$(MSBuildProjectName) + .\bin\ + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Audio/IAudioClient.cs b/discord.net/src/Discord.Net.Audio/IAudioClient.cs new file mode 100644 index 000000000..71c8783d5 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/IAudioClient.cs @@ -0,0 +1,48 @@ +using Discord.Net.Rest; +using Discord.Net.WebSockets; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Audio +{ + public interface IAudioClient + { + /// Gets the unique identifier for this client. + int Id { get; } + /// Gets the session id for the current connection. + string SessionId { get; } + /// Gets the current state of this client. + ConnectionState State { get; } + /// Gets the channel this client is currently a member of. + Channel Channel { get; } + /// Gets the server this client is bound to. + Server Server { get; } + /// Gets a stream object that wraps the Send() function. + Stream OutputStream { get; } + /// Gets a cancellation token that triggers when the client is manually disconnected. + CancellationToken CancelToken { get; } + + /// Gets the internal RestClient for the Client API endpoint. + RestClient ClientAPI { get; } + /// Gets the internal WebSocket for the Gateway event stream. + GatewaySocket GatewaySocket { get; } + /// Gets the internal WebSocket for the Voice control stream. + VoiceSocket VoiceSocket { get; } + + /// Moves the client to another channel on the same server. + Task Join(Channel channel); + /// Disconnects from the Discord server, canceling any pending requests. + Task Disconnect(); + + /// Sends a PCM frame to the voice server. Will block until space frees up in the outgoing buffer. + /// PCM frame to send. This must be a single or collection of uncompressed 48Kz monochannel 20ms PCM frames. + /// Offset . + /// Number of bytes in this frame. + void Send(byte[] data, int offset, int count); + /// Clears the PCM buffer. + void Clear(); + /// Blocks until the voice output buffer is empty. + void Wait(); + } +} diff --git a/discord.net/src/Discord.Net.Audio/InternalFrameEventArgs.cs b/discord.net/src/Discord.Net.Audio/InternalFrameEventArgs.cs new file mode 100644 index 000000000..b74dc8295 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/InternalFrameEventArgs.cs @@ -0,0 +1,22 @@ +using System; + +namespace Discord +{ + internal class InternalFrameEventArgs : EventArgs + { + public ulong UserId { get; } + public ulong ChannelId { get; } + public byte[] Buffer { get; } + public int Offset { get; } + public int Count { get; } + + public InternalFrameEventArgs(ulong userId, ulong channelId, byte[] buffer, int offset, int count) + { + UserId = userId; + ChannelId = channelId; + Buffer = buffer; + Offset = offset; + Count = count; + } + } +} diff --git a/discord.net/src/Discord.Net.Audio/InternalIsSpeakingEventArgs.cs b/discord.net/src/Discord.Net.Audio/InternalIsSpeakingEventArgs.cs new file mode 100644 index 000000000..641e863f4 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/InternalIsSpeakingEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord.Audio +{ + internal class InternalIsSpeakingEventArgs + { + public ulong UserId { get; } + public bool IsSpeaking { get; } + + public InternalIsSpeakingEventArgs(ulong userId, bool isSpeaking) + { + UserId = userId; + IsSpeaking = isSpeaking; + } + } +} diff --git a/discord.net/src/Discord.Net.Audio/Net/VoiceSocket.cs b/discord.net/src/Discord.Net.Audio/Net/VoiceSocket.cs new file mode 100644 index 000000000..692cc4a5d --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/Net/VoiceSocket.cs @@ -0,0 +1,516 @@ +using Discord.API.Client; +using Discord.API.Client.VoiceSocket; +using Discord.Audio; +using Discord.Audio.Opus; +using Discord.Audio.Sodium; +using Discord.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + public partial class VoiceSocket : WebSocket + { + private const int MaxOpusSize = 4000; + private const string EncryptedMode = "xsalsa20_poly1305"; + private const string UnencryptedMode = "plain"; + + private readonly int _targetAudioBufferLength; + private readonly ConcurrentDictionary _decoders; + private readonly AudioServiceConfig _audioConfig; + private Task _sendTask, _receiveTask; + private VoiceBuffer _sendBuffer; + private OpusEncoder _encoder; + private uint _ssrc; + private ConcurrentDictionary _ssrcMapping; + private UdpClient _udp; + private IPEndPoint _endpoint; + private bool _isEncrypted; + private byte[] _secretKey, _encodingBuffer; + private ushort _sequence; + private string _encryptionMode; + private int _ping; + private ulong? _userId; + private string _sessionId; + + public string Token { get; internal set; } + public Server Server { get; internal set; } + public Channel Channel { get; internal set; } + + public int Ping => _ping; + internal VoiceBuffer OutputBuffer => _sendBuffer; + + internal event EventHandler UserIsSpeaking = delegate { }; + internal event EventHandler FrameReceived = delegate { }; + + private void OnUserIsSpeaking(ulong userId, bool isSpeaking) + => UserIsSpeaking(this, new InternalIsSpeakingEventArgs(userId, isSpeaking)); + internal void OnFrameReceived(ulong userId, ulong channelId, byte[] buffer, int offset, int count) + => FrameReceived(this, new InternalFrameEventArgs(userId, channelId, buffer, offset, count)); + + internal VoiceSocket(DiscordConfig config, AudioServiceConfig audioConfig, JsonSerializer serializer, Logger logger) + : base(config, serializer, logger) + { + _audioConfig = audioConfig; + _decoders = new ConcurrentDictionary(); + _targetAudioBufferLength = _audioConfig.BufferLength / 20; //20 ms frames + _encodingBuffer = new byte[MaxOpusSize]; + _ssrcMapping = new ConcurrentDictionary(); + _encoder = new OpusEncoder(48000, _audioConfig.Channels, 20, _audioConfig.Bitrate, OpusApplication.MusicOrMixed); + _sendBuffer = new VoiceBuffer((int)Math.Ceiling(_audioConfig.BufferLength / (double)_encoder.FrameLength), _encoder.FrameSize); + } + + public Task Connect(string host, string token, ulong userId, string sessionId, CancellationToken parentCancelToken) + { + Host = host; + Token = token; + _userId = userId; + _sessionId = sessionId; + return BeginConnect(parentCancelToken); + } + private async Task Reconnect() + { + try + { + var cancelToken = _parentCancelToken; + await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false); + while (!cancelToken.IsCancellationRequested) + { + try + { + await BeginConnect(_parentCancelToken).ConfigureAwait(false); + break; + } + catch (OperationCanceledException) { throw; } + catch (Exception ex) + { + Logger.Error("Reconnect failed", ex); + //Net is down? We can keep trying to reconnect until the user runs Disconnect() + await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException) { } + } + public async Task Disconnect() + { + await _taskManager.Stop(true).ConfigureAwait(false); + _userId = null; + } + + protected override async Task Run() + { + _udp = new UdpClient(new IPEndPoint(IPAddress.Any, 0)); + + List tasks = new List(); + if (_audioConfig.Mode.HasFlag(AudioMode.Outgoing)) + _sendTask = Task.Run(() => SendVoiceAsync(CancelToken)); + _receiveTask = Task.Run(() => ReceiveVoiceAsync(CancelToken)); + + SendIdentify(_userId.Value, _sessionId); + +#if !NETSTANDARD1_3 + tasks.Add(WatcherAsync()); +#endif + tasks.AddRange(_engine.GetTasks(CancelToken)); + tasks.Add(HeartbeatAsync(CancelToken)); + await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false); + } + protected override async Task Cleanup() + { + var sendThread = _sendTask; + if (sendThread != null) + { + try { await sendThread.ConfigureAwait(false); } + catch (Exception) { } //Ignore any errors during cleanup + } + _sendTask = null; + + var receiveThread = _receiveTask; + if (receiveThread != null) + { + try { await receiveThread.ConfigureAwait(false); } + catch (Exception) { } //Ignore any errors during cleanup + } + _receiveTask = null; + + OpusDecoder decoder; + foreach (var pair in _decoders) + { + if (_decoders.TryRemove(pair.Key, out decoder)) + decoder.Dispose(); + } + + ClearPCMFrames(); + _udp = null; + + await base.Cleanup().ConfigureAwait(false); + } + + private async Task ReceiveVoiceAsync(CancellationToken cancelToken) + { + var closeTask = cancelToken.Wait(); + try + { + byte[] packet, decodingBuffer = null, nonce = null, result; + int packetLength, resultOffset, resultLength; + IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); + + if ((_audioConfig.Mode & AudioMode.Incoming) != 0) + { + decodingBuffer = new byte[MaxOpusSize]; + nonce = new byte[24]; + } + + while (!cancelToken.IsCancellationRequested) + { + await Task.Delay(1).ConfigureAwait(false); + if (_udp.Available > 0) + { +#if !NETSTANDARD1_3 + packet = _udp.Receive(ref endpoint); +#else + //TODO: Is this really the only way to end a Receive call in DOTNET5_4? + var receiveTask = _udp.ReceiveAsync(); + var task = Task.WhenAny(closeTask, receiveTask).Result; + if (task == closeTask) + break; + var udpPacket = receiveTask.Result; + packet = udpPacket.Buffer; + endpoint = udpPacket.RemoteEndPoint; +#endif + packetLength = packet.Length; + + if (packetLength > 0 && endpoint.Equals(_endpoint)) + { + if (State != ConnectionState.Connected) + { + if (packetLength != 70) + return; + + string ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0'); + int port = packet[68] | packet[69] << 8; + + SendSelectProtocol(ip, port); + if ((_audioConfig.Mode & AudioMode.Incoming) == 0) + return; //We dont need this thread anymore + } + else + { + //Parse RTP Data + if (packetLength < 12) return; + if (packet[0] != 0x80) return; //Flags + if (packet[1] != 0x78) return; //Payload Type + + ushort sequenceNumber = (ushort)((packet[2] << 8) | + packet[3] << 0); + uint timestamp = (uint)((packet[4] << 24) | + (packet[5] << 16) | + (packet[6] << 8) | + (packet[7] << 0)); + uint ssrc = (uint)((packet[8] << 24) | + (packet[9] << 16) | + (packet[10] << 8) | + (packet[11] << 0)); + + //Decrypt + if (_isEncrypted) + { + if (packetLength < 28) //12 + 16 (RTP + Poly1305 MAC) + return; + + Buffer.BlockCopy(packet, 0, nonce, 0, 12); + int ret = SecretBox.Decrypt(packet, 12, packetLength - 12, decodingBuffer, nonce, _secretKey); + if (ret != 0) + continue; + result = decodingBuffer; + resultOffset = 0; + resultLength = packetLength - 28; + } + else //Plain + { + result = packet; + resultOffset = 12; + resultLength = packetLength - 12; + } + + /*if (_logLevel >= LogMessageSeverity.Debug) + RaiseOnLog(LogMessageSeverity.Debug, $"Received {buffer.Length - 12} bytes.");*/ + + ulong userId; + if (_ssrcMapping.TryGetValue(ssrc, out userId)) + OnFrameReceived(userId, Channel.Id, result, resultOffset, resultLength); + } + } + } + } + } + catch (OperationCanceledException) { } + catch (InvalidOperationException) { } //Includes ObjectDisposedException + } + + private async Task SendVoiceAsync(CancellationToken cancelToken) + { + try + { + while (!cancelToken.IsCancellationRequested && State != ConnectionState.Connected) + await Task.Delay(1).ConfigureAwait(false); + + if (cancelToken.IsCancellationRequested) + return; + + byte[] frame = new byte[_encoder.FrameSize]; + byte[] encodedFrame = new byte[MaxOpusSize]; + byte[] voicePacket, pingPacket, nonce = null; + uint timestamp = 0; + double nextTicks = 0.0, nextPingTicks = 0.0; + long ticksPerSeconds = Stopwatch.Frequency; + double ticksPerMillisecond = Stopwatch.Frequency / 1000.0; + double ticksPerFrame = ticksPerMillisecond * _encoder.FrameLength; + double spinLockThreshold = 3 * ticksPerMillisecond; + uint samplesPerFrame = (uint)_encoder.SamplesPerFrame; + Stopwatch sw = Stopwatch.StartNew(); + + if (_isEncrypted) + { + nonce = new byte[24]; + voicePacket = new byte[MaxOpusSize + 12 + 16]; + } + else + voicePacket = new byte[MaxOpusSize + 12]; + + pingPacket = new byte[8]; + + int rtpPacketLength = 0; + voicePacket[0] = 0x80; //Flags; + voicePacket[1] = 0x78; //Payload Type + voicePacket[8] = (byte)(_ssrc >> 24); + voicePacket[9] = (byte)(_ssrc >> 16); + voicePacket[10] = (byte)(_ssrc >> 8); + voicePacket[11] = (byte)(_ssrc >> 0); + + if (_isEncrypted) + Buffer.BlockCopy(voicePacket, 0, nonce, 0, 12); + + bool hasFrame = false; + while (!cancelToken.IsCancellationRequested) + { + if (!hasFrame && _sendBuffer.Pop(frame)) + { + ushort sequence = unchecked(_sequence++); + voicePacket[2] = (byte)(sequence >> 8); + voicePacket[3] = (byte)(sequence >> 0); + voicePacket[4] = (byte)(timestamp >> 24); + voicePacket[5] = (byte)(timestamp >> 16); + voicePacket[6] = (byte)(timestamp >> 8); + voicePacket[7] = (byte)(timestamp >> 0); + + //Encode + int encodedLength = _encoder.EncodeFrame(frame, 0, encodedFrame); + + //Encrypt + if (_isEncrypted) + { + Buffer.BlockCopy(voicePacket, 2, nonce, 2, 6); //Update nonce + int ret = SecretBox.Encrypt(encodedFrame, encodedLength, voicePacket, 12, nonce, _secretKey); + if (ret != 0) + continue; + rtpPacketLength = encodedLength + 12 + 16; + } + else + { + Buffer.BlockCopy(encodedFrame, 0, voicePacket, 12, encodedLength); + rtpPacketLength = encodedLength + 12; + } + + timestamp = unchecked(timestamp + samplesPerFrame); + hasFrame = true; + } + + long currentTicks = sw.ElapsedTicks; + double ticksToNextFrame = nextTicks - currentTicks; + if (ticksToNextFrame <= 0.0) + { + if (hasFrame) + { + try + { + await _udp.SendAsync(voicePacket, rtpPacketLength, _endpoint).ConfigureAwait(false); + } + catch (SocketException ex) + { + Logger.Error("Failed to send UDP packet.", ex); + } + hasFrame = false; + } + nextTicks += ticksPerFrame; + + //Is it time to send out another ping? + if (currentTicks > nextPingTicks) + { + //Increment in LE + for (int i = 0; i < 8; i++) + { + var b = pingPacket[i]; + if (b == byte.MaxValue) + pingPacket[i] = 0; + else + { + pingPacket[i] = (byte)(b + 1); + break; + } + } + await _udp.SendAsync(pingPacket, pingPacket.Length, _endpoint).ConfigureAwait(false); + nextPingTicks = currentTicks + 5 * ticksPerSeconds; + } + } + else + { + if (hasFrame) + { + int time = (int)Math.Floor(ticksToNextFrame / ticksPerMillisecond); + if (time > 0) + await Task.Delay(time).ConfigureAwait(false); + } + else + await Task.Delay(1).ConfigureAwait(false); //Give as much time to the encrypter as possible + } + } + } + catch (OperationCanceledException) { } + catch (InvalidOperationException) { } //Includes ObjectDisposedException + } +#if !NETSTANDARD1_3 + //Closes the UDP socket when _disconnectToken is triggered, since UDPClient doesn't allow passing a canceltoken + private async Task WatcherAsync() + { + await CancelToken.Wait().ConfigureAwait(false); + _udp.Close(); + } +#endif + + protected override async Task ProcessMessage(string json) + { + await base.ProcessMessage(json).ConfigureAwait(false); + + WebSocketMessage msg; + using (var reader = new JsonTextReader(new StringReader(json))) + msg = _serializer.Deserialize(reader, typeof(WebSocketMessage)) as WebSocketMessage; + + var opCode = (OpCodes)msg.Operation; + switch (opCode) + { + case OpCodes.Ready: + { + if (State != ConnectionState.Connected) + { + var payload = (msg.Payload as JToken).ToObject(_serializer); + _heartbeatInterval = payload.HeartbeatInterval; + _ssrc = payload.SSRC; + string hostname = Host.Replace("wss://", ""); + var address = (await Dns.GetHostAddressesAsync(hostname).ConfigureAwait(false)).FirstOrDefault(); + _endpoint = new IPEndPoint(address, payload.Port); + + if (_audioConfig.EnableEncryption) + { + if (payload.Modes.Contains(EncryptedMode)) + { + _encryptionMode = EncryptedMode; + _isEncrypted = true; + } + else + throw new InvalidOperationException("Unexpected encryption format."); + } + else + { + _encryptionMode = UnencryptedMode; + _isEncrypted = false; + } + + _sequence = 0;// (ushort)_rand.Next(0, ushort.MaxValue); + //No thread issue here because SendAsync doesn't start until _isReady is true + byte[] packet = new byte[70]; + packet[0] = (byte)(_ssrc >> 24); + packet[1] = (byte)(_ssrc >> 16); + packet[2] = (byte)(_ssrc >> 8); + packet[3] = (byte)(_ssrc >> 0); + await _udp.SendAsync(packet, 70, _endpoint).ConfigureAwait(false); + } + } + break; + case OpCodes.Heartbeat: + { + long time = EpochTime.GetMilliseconds(); + var payload = (long)msg.Payload; + _ping = (int)(payload - time); + //TODO: Use this to estimate latency + } + break; + case OpCodes.SessionDescription: + { + var payload = (msg.Payload as JToken).ToObject(_serializer); + _secretKey = payload.SecretKey; + SendSetSpeaking(true); + await EndConnect().ConfigureAwait(false); + } + break; + case OpCodes.Speaking: + { + var payload = (msg.Payload as JToken).ToObject(_serializer); + OnUserIsSpeaking(payload.UserId, payload.IsSpeaking); + } + break; + default: + Logger.Warning($"Unknown Opcode: {opCode}"); + break; + } + } + + public void SendPCMFrames(byte[] data, int offset, int count) + { + _sendBuffer.Push(data, offset, count, CancelToken); + } + public void ClearPCMFrames() + { + _sendBuffer.Clear(CancelToken); + } + + public void WaitForQueue() + { + _sendBuffer.Wait(CancelToken); + } + + public override void SendHeartbeat() + => QueueMessage(new HeartbeatCommand()); + public void SendIdentify(ulong id, string sessionId) + => QueueMessage(new IdentifyCommand + { + GuildId = Server.Id, + UserId = id, + SessionId = sessionId, + Token = Token + }); + public void SendSelectProtocol(string externalAddress, int externalPort) + => QueueMessage(new SelectProtocolCommand + { + Protocol = "udp", + ExternalAddress = externalAddress, + ExternalPort = externalPort, + EncryptionMode = _encryptionMode + }); + public void SendSetSpeaking(bool value) + => QueueMessage(new SetSpeakingCommand { IsSpeaking = value, Delay = 0 }); + + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Audio/Opus/OpusConverter.cs b/discord.net/src/Discord.Net.Audio/Opus/OpusConverter.cs new file mode 100644 index 000000000..d93337138 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/Opus/OpusConverter.cs @@ -0,0 +1,111 @@ +using System; +using System.Runtime.InteropServices; +#if NET45 +using System.Security; +#endif + +namespace Discord.Audio.Opus +{ + internal enum OpusApplication : int + { + Voice = 2048, + MusicOrMixed = 2049, + LowLatency = 2051 + } + internal enum OpusError : int + { + OK = 0, + BadArg = -1, + BufferToSmall = -2, + InternalError = -3, + InvalidPacket = -4, + Unimplemented = -5, + InvalidState = -6, + AllocFail = -7 + } + + internal abstract class OpusConverter : IDisposable + { + protected enum Ctl : int + { + SetBitrateRequest = 4002, + GetBitrateRequest = 4003, + SetInbandFECRequest = 4012, + GetInbandFECRequest = 4013 + } + +#if NET45 + [SuppressUnmanagedCodeSecurity] +#endif + protected unsafe static class UnsafeNativeMethods + { + [DllImport("opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateEncoder(int Fs, int channels, int application, out OpusError error); + [DllImport("opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyEncoder(IntPtr encoder); + [DllImport("opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)] + public static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte[] data, int max_data_bytes); + [DllImport("opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)] + public static extern int EncoderCtl(IntPtr st, Ctl request, int value); + + [DllImport("opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr CreateDecoder(int Fs, int channels, out OpusError error); + [DllImport("opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)] + public static extern void DestroyDecoder(IntPtr decoder); + [DllImport("opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)] + public static extern int Decode(IntPtr st, byte* data, int len, byte[] pcm, int frame_size, int decode_fec); + } + + protected IntPtr _ptr; + + /// Gets the bit rate of this converter. + public const int BitsPerSample = 16; + /// Gets the input sampling rate of this converter. + public int InputSamplingRate { get; } + /// Gets the number of channels of this converter. + public int InputChannels { get; } + /// Gets the milliseconds per frame. + public int FrameLength { get; } + /// Gets the number of samples per frame. + public int SamplesPerFrame { get; } + /// Gets the bytes per frame. + public int FrameSize { get; } + /// Gets the bytes per sample. + public int SampleSize { get; } + + protected OpusConverter(int samplingRate, int channels, int frameLength) + { + if (samplingRate != 8000 && samplingRate != 12000 && + samplingRate != 16000 && samplingRate != 24000 && + samplingRate != 48000) + throw new ArgumentOutOfRangeException(nameof(samplingRate)); + if (channels != 1 && channels != 2) + throw new ArgumentOutOfRangeException(nameof(channels)); + + InputSamplingRate = samplingRate; + InputChannels = channels; + FrameLength = frameLength; + SampleSize = (BitsPerSample / 8) * channels; + SamplesPerFrame = samplingRate / 1000 * FrameLength; + FrameSize = SamplesPerFrame * SampleSize; + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + disposedValue = true; + } + ~OpusConverter() { + Dispose(false); + } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/discord.net/src/Discord.Net.Audio/Opus/OpusDecoder.cs b/discord.net/src/Discord.Net.Audio/Opus/OpusDecoder.cs new file mode 100644 index 000000000..d8e6b8087 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/Opus/OpusDecoder.cs @@ -0,0 +1,43 @@ +using System; + +namespace Discord.Audio.Opus +{ + internal class OpusDecoder : OpusConverter + { + /// Creates a new Opus decoder. + /// Sampling rate of the input PCM (in Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000 + /// Length, in milliseconds, of each frame. Supported Values: 2.5, 5, 10, 20, 40, or 60 + public OpusDecoder(int samplingRate, int channels, int frameLength) + : base(samplingRate, channels, frameLength) + { + OpusError error; + _ptr = UnsafeNativeMethods.CreateDecoder(samplingRate, channels, out error); + if (error != OpusError.OK) + throw new InvalidOperationException($"Error occured while creating decoder: {error}"); + } + + /// Produces PCM samples from Opus-encoded audio. + /// PCM samples to decode. + /// Offset of the frame in input. + /// Buffer to store the decoded frame. + public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output) + { + int result = 0; + fixed (byte* inPtr = input) + result = UnsafeNativeMethods.Decode(_ptr, inPtr + inputOffset, inputCount, output, SamplesPerFrame, 0); + + if (result < 0) + throw new Exception(((OpusError)result).ToString()); + return result; + } + + protected override void Dispose(bool disposing) + { + if (_ptr != IntPtr.Zero) + { + UnsafeNativeMethods.DestroyDecoder(_ptr); + _ptr = IntPtr.Zero; + } + } + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Audio/Opus/OpusEncoder.cs b/discord.net/src/Discord.Net.Audio/Opus/OpusEncoder.cs new file mode 100644 index 000000000..be0623c6b --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/Opus/OpusEncoder.cs @@ -0,0 +1,78 @@ +using System; + +namespace Discord.Audio.Opus +{ + internal class OpusEncoder : OpusConverter + { + /// Gets the bit rate in kbit/s. + public int? BitRate { get; } + /// Gets the coding mode of the encoder. + public OpusApplication Application { get; } + + /// Creates a new Opus encoder. + /// Sampling rate of the input signal (Hz). Supported Values: 8000, 12000, 16000, 24000, or 48000 + /// Number of channels in input signal. Supported Values: 1 or 2 + /// Length, in milliseconds, that each frame takes. Supported Values: 2.5, 5, 10, 20, 40, 60 + /// Bitrate (kbit/s) used for this encoder. Supported Values: 1-512. Null will use the recommended bitrate. + /// Coding mode. + public OpusEncoder(int samplingRate, int channels, int frameLength, int? bitrate, OpusApplication application) + : base(samplingRate, channels, frameLength) + { + if (bitrate != null && (bitrate < 1 || bitrate > AudioServiceConfig.MaxBitrate)) + throw new ArgumentOutOfRangeException(nameof(bitrate)); + + BitRate = bitrate; + Application = application; + + OpusError error; + _ptr = UnsafeNativeMethods.CreateEncoder(samplingRate, channels, (int)application, out error); + if (error != OpusError.OK) + throw new InvalidOperationException($"Error occured while creating encoder: {error}"); + + SetForwardErrorCorrection(true); + if (bitrate != null) + SetBitrate(bitrate.Value); + } + + /// Produces Opus encoded audio from PCM samples. + /// PCM samples to encode. + /// Offset of the frame in pcmSamples. + /// Buffer to store the encoded frame. + /// Length of the frame contained in outputBuffer. + public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output) + { + int result = 0; + fixed (byte* inPtr = input) + result = UnsafeNativeMethods.Encode(_ptr, inPtr + inputOffset, SamplesPerFrame, output, output.Length); + + if (result < 0) + throw new Exception(((OpusError)result).ToString()); + return result; + } + + /// Gets or sets whether Forward Error Correction is enabled. + public void SetForwardErrorCorrection(bool value) + { + var result = UnsafeNativeMethods.EncoderCtl(_ptr, Ctl.SetInbandFECRequest, value ? 1 : 0); + if (result < 0) + throw new Exception(((OpusError)result).ToString()); + } + + /// Gets or sets whether Forward Error Correction is enabled. + public void SetBitrate(int value) + { + var result = UnsafeNativeMethods.EncoderCtl(_ptr, Ctl.SetBitrateRequest, value * 1000); + if (result < 0) + throw new Exception(((OpusError)result).ToString()); + } + + protected override void Dispose(bool disposing) + { + if (_ptr != IntPtr.Zero) + { + UnsafeNativeMethods.DestroyEncoder(_ptr); + _ptr = IntPtr.Zero; + } + } + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Audio/Sodium/SecretBox.cs b/discord.net/src/Discord.Net.Audio/Sodium/SecretBox.cs new file mode 100644 index 000000000..f73093316 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/Sodium/SecretBox.cs @@ -0,0 +1,32 @@ +using System.Runtime.InteropServices; +#if NET45 +using System.Security; +#endif + +namespace Discord.Audio.Sodium +{ + internal unsafe static class SecretBox + { +#if NET45 + [SuppressUnmanagedCodeSecurity] +#endif + private static class SafeNativeMethods + { + [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] + public static extern int SecretBoxEasy(byte* output, byte[] input, long inputLength, byte[] nonce, byte[] secret); + [DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)] + public static extern int SecretBoxOpenEasy(byte[] output, byte* input, long inputLength, byte[] nonce, byte[] secret); + } + + public static int Encrypt(byte[] input, long inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) + { + fixed (byte* outPtr = output) + return SafeNativeMethods.SecretBoxEasy(outPtr + outputOffset, input, inputLength, nonce, secret); + } + public static int Decrypt(byte[] input, int inputOffset, long inputLength, byte[] output, byte[] nonce, byte[] secret) + { + fixed (byte* inPtr = input) + return SafeNativeMethods.SecretBoxOpenEasy(output, inPtr + inputLength, inputLength, nonce, secret); + } + } +} diff --git a/discord.net/src/Discord.Net.Audio/UserIsTalkingEventArgs.cs b/discord.net/src/Discord.Net.Audio/UserIsTalkingEventArgs.cs new file mode 100644 index 000000000..698f44d4c --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/UserIsTalkingEventArgs.cs @@ -0,0 +1,13 @@ +namespace Discord +{ + public class UserIsSpeakingEventArgs : UserEventArgs + { + public bool IsSpeaking { get; } + + public UserIsSpeakingEventArgs(User user, bool isSpeaking) + : base(user) + { + IsSpeaking = isSpeaking; + } + } +} diff --git a/discord.net/src/Discord.Net.Audio/VirtualClient.cs b/discord.net/src/Discord.Net.Audio/VirtualClient.cs new file mode 100644 index 000000000..12d285a0d --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/VirtualClient.cs @@ -0,0 +1,40 @@ +using Discord.Net.Rest; +using Discord.Net.WebSockets; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Audio +{ + internal class VirtualClient : IAudioClient + { + private readonly AudioClient _client; + + public Server Server { get; } + + public int Id => 0; + public string SessionId => _client.Server == Server ? _client.SessionId : null; + + public ConnectionState State => _client.Server == Server ? _client.State : ConnectionState.Disconnected; + public Channel Channel => _client.Server == Server ? _client.Channel : null; + public Stream OutputStream => _client.Server == Server ? _client.OutputStream : null; + public CancellationToken CancelToken => _client.Server == Server ? _client.CancelToken : CancellationToken.None; + + public RestClient ClientAPI => _client.Server == Server ? _client.ClientAPI : null; + public GatewaySocket GatewaySocket => _client.Server == Server ? _client.GatewaySocket : null; + public VoiceSocket VoiceSocket => _client.Server == Server ? _client.VoiceSocket : null; + + public VirtualClient(AudioClient client, Server server) + { + _client = client; + Server = server; + } + + public Task Disconnect() => _client.Service.Leave(Server); + public Task Join(Channel channel) => _client.Join(channel); + + public void Send(byte[] data, int offset, int count) => _client.Send(data, offset, count); + public void Clear() => _client.Clear(); + public void Wait() => _client.Wait(); + } +} diff --git a/discord.net/src/Discord.Net.Audio/VoiceBuffer.cs b/discord.net/src/Discord.Net.Audio/VoiceBuffer.cs new file mode 100644 index 000000000..054ab81a0 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/VoiceBuffer.cs @@ -0,0 +1,140 @@ +using Nito.AsyncEx; +using System; +using System.Threading; + +namespace Discord.Audio +{ + internal class VoiceBuffer + { + private readonly int _frameSize, _frameCount, _bufferSize; + private readonly byte[] _buffer; + private readonly byte[] _blankFrame; + private ushort _readCursor, _writeCursor; + private ManualResetEventSlim _notOverflowEvent; + private bool _isClearing; + private AsyncLock _lock; + + public int FrameSize => _frameSize; + public int FrameCount => _frameCount; + public ushort ReadPos => _readCursor; + public ushort WritePos => _writeCursor; + + public VoiceBuffer(int frameCount, int frameSize) + { + _frameSize = frameSize; + _frameCount = frameCount; + _bufferSize = _frameSize * _frameCount; + _readCursor = 0; + _writeCursor = 0; + _buffer = new byte[_bufferSize]; + _blankFrame = new byte[_frameSize]; + _notOverflowEvent = new ManualResetEventSlim(); //Notifies when an overflow is solved + _lock = new AsyncLock(); + } + + public void Push(byte[] buffer, int offset, int count, CancellationToken cancelToken) + { + if (cancelToken.IsCancellationRequested) + throw new OperationCanceledException("Client is disconnected.", cancelToken); + + int wholeFrames = count / _frameSize; + int expectedBytes = wholeFrames * _frameSize; + int lastFrameSize = count - expectedBytes; + + using (_lock.Lock()) + { + for (int i = 0, pos = offset; i <= wholeFrames; i++, pos += _frameSize) + { + //If the read cursor is in the next position, wait for it to move. + ushort nextPosition = _writeCursor; + AdvanceCursorPos(ref nextPosition); + if (_readCursor == nextPosition) + { + _notOverflowEvent.Reset(); + try + { + _notOverflowEvent.Wait(cancelToken); + } + catch (OperationCanceledException ex) + { + throw new OperationCanceledException("Client is disconnected.", ex, cancelToken); + } + } + + if (i == wholeFrames) + { + //If there are no partial frames, skip this step + if (lastFrameSize == 0) + break; + + //Copy partial frame + Buffer.BlockCopy(buffer, pos, _buffer, _writeCursor * _frameSize, lastFrameSize); + + //Wipe the end of the buffer + Buffer.BlockCopy(_blankFrame, 0, _buffer, _writeCursor * _frameSize + lastFrameSize, _frameSize - lastFrameSize); + } + else + { + //Copy full frame + Buffer.BlockCopy(buffer, pos, _buffer, _writeCursor * _frameSize, _frameSize); + } + + //Advance the write cursor to the next position + AdvanceCursorPos(ref _writeCursor); + } + } + } + + public bool Pop(byte[] buffer) + { + //using (_lock.Lock()) + //{ + if (_writeCursor == _readCursor) + { + _notOverflowEvent.Set(); + return false; + } + + bool isClearing = _isClearing; + if (!isClearing) + Buffer.BlockCopy(_buffer, _readCursor * _frameSize, buffer, 0, _frameSize); + + //Advance the read cursor to the next position + AdvanceCursorPos(ref _readCursor); + _notOverflowEvent.Set(); + return !isClearing; + //} + } + + public void Clear(CancellationToken cancelToken) + { + using (_lock.Lock()) + { + _isClearing = true; + for (int i = 0; i < _frameCount; i++) + Buffer.BlockCopy(_blankFrame, 0, _buffer, i * _frameCount, i++); + + _writeCursor = 0; + _readCursor = 0; + _isClearing = false; + } + } + + public void Wait(CancellationToken cancelToken) + { + while (true) + { + _notOverflowEvent.Wait(cancelToken); + if (_writeCursor == _readCursor) + break; + } + } + + private void AdvanceCursorPos(ref ushort pos) + { + pos++; + if (pos == _frameCount) + pos = 0; + } + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Audio/VoiceDisconnectedEventArgs.cs b/discord.net/src/Discord.Net.Audio/VoiceDisconnectedEventArgs.cs new file mode 100644 index 000000000..4f46abde2 --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/VoiceDisconnectedEventArgs.cs @@ -0,0 +1,15 @@ +using System; + +namespace Discord +{ + public class VoiceDisconnectedEventArgs : DisconnectedEventArgs + { + public ulong ServerId { get; } + + public VoiceDisconnectedEventArgs(ulong serverId, bool wasUnexpected, Exception ex) + : base(wasUnexpected, ex) + { + ServerId = serverId; + } + } +} diff --git a/discord.net/src/Discord.Net.Audio/project.json b/discord.net/src/Discord.Net.Audio/project.json new file mode 100644 index 000000000..e754bbcde --- /dev/null +++ b/discord.net/src/Discord.Net.Audio/project.json @@ -0,0 +1,40 @@ +{ + "version": "0.9.2", + "description": "A Discord.Net extension adding voice support.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "projectUrl": "https://github.com/RogueException/Discord.Net", + "licenseUrl": "http://opensource.org/licenses/MIT", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + }, + "contentFiles": [ "libsodium.dll", "opus.dll" ] + }, + + "buildOptions": { + "compile": [ "**/*.cs", "../Discord.Net.Shared/*.cs" ], + "allowUnsafe": true, + "warningsAsErrors": true + }, + + "dependencies": { + "Discord.Net": "0.9.2" + }, + + "frameworks": { + "netstandard1.3": { + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027" + }, + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + }, + "net45": { } + } +} diff --git a/discord.net/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj b/discord.net/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj new file mode 100644 index 000000000..e3ce3c79a --- /dev/null +++ b/discord.net/src/Discord.Net.Commands.Net45/Discord.Net.Commands.csproj @@ -0,0 +1,148 @@ + + + + + Debug + AnyCPU + {1B5603B4-6F8F-4289-B945-7BAAE523D740} + Library + Properties + Discord.Commands + Discord.Net.Commands + 512 + v4.5 + False + + + + true + full + false + bin\Debug\ + TRACE;DEBUG;NET45 + prompt + 4 + 6 + true + + + pdbonly + true + bin\Release\ + TRACE;NET45 + prompt + 4 + true + 6 + + + + + + + Command.cs + + + CommandBuilder.cs + + + CommandErrorEventArgs.cs + + + CommandEventArgs.cs + + + CommandExtensions.cs + + + CommandMap.cs + + + CommandParameter.cs + + + CommandParser.cs + + + CommandService.cs + + + CommandServiceConfig.cs + + + HelpMode.cs + + + Permissions\GenericPermissionChecker.cs + + + Permissions\IPermissionChecker.cs + + + Permissions\Levels\PermissionLevelChecker.cs + + + Permissions\Levels\PermissionLevelExtensions.cs + + + Permissions\Levels\PermissionLevelService.cs + + + Permissions\Users\BlacklistChecker.cs + + + Permissions\Users\BlacklistExtensions.cs + + + Permissions\Users\BlacklistService.cs + + + Permissions\Users\UserlistService.cs + + + Permissions\Users\WhitelistChecker.cs + + + Permissions\Users\WhitelistExtensions.cs + + + Permissions\Users\WhitelistService.cs + + + Permissions\Visibility\PrivateChecker.cs + + + Permissions\Visibility\PrivateExtensions.cs + + + Permissions\Visibility\PublicChecker.cs + + + Permissions\Visibility\PublicExtensions.cs + + + + + + {8d71a857-879a-4a10-859e-5ff824ed6688} + Discord.Net + + + + + + + + project.json + + + + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs b/discord.net/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..11e00230c --- /dev/null +++ b/discord.net/src/Discord.Net.Commands.Net45/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Discord.Net.Commands")] +[assembly: AssemblyDescription("A Discord.Net extension adding basic command support.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("RogueException")] +[assembly: AssemblyProduct("Discord.Net.Commands")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] +[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] + +[assembly: AssemblyVersion("0.9.2.0")] +[assembly: AssemblyFileVersion("0.9.2.0")] + diff --git a/discord.net/src/Discord.Net.Commands.Net45/project.json b/discord.net/src/Discord.Net.Commands.Net45/project.json new file mode 100644 index 000000000..62e5e6154 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands.Net45/project.json @@ -0,0 +1,10 @@ +{ + "frameworks": { + "net45": { } + }, + "runtimes": { + "win": { }, + "win-x86": { }, + "win-x64": { } + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Commands/Command.cs b/discord.net/src/Discord.Net.Commands/Command.cs new file mode 100644 index 000000000..a8addc1b1 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Command.cs @@ -0,0 +1,83 @@ +using Discord.Commands.Permissions; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + //TODO: Make this more friendly and expose it to be extendable + public class Command + { + private string[] _aliases; + internal CommandParameter[] _parameters; + private IPermissionChecker[] _checks; + private Func _runFunc; + internal readonly Dictionary _parametersByName; + + public string Text { get; } + public string Category { get; internal set; } + public bool IsHidden { get; internal set; } + public string Description { get; internal set; } + + public IEnumerable Aliases => _aliases; + public IEnumerable Parameters => _parameters; + public CommandParameter this[string name] => _parametersByName[name]; + + internal Command(string text) + { + Text = text; + IsHidden = false; + _aliases = new string[0]; + _parameters = new CommandParameter[0]; + _parametersByName = new Dictionary(); + } + + + internal void SetAliases(string[] aliases) + { + _aliases = aliases; + } + internal void SetParameters(CommandParameter[] parameters) + { + _parametersByName.Clear(); + for (int i = 0; i < parameters.Length; i++) + { + parameters[i].Id = i; + _parametersByName[parameters[i].Name] = parameters[i]; + } + _parameters = parameters; + } + internal void SetChecks(IPermissionChecker[] checks) + { + _checks = checks; + } + + internal bool CanRun(User user, Channel channel, out string error) + { + for (int i = 0; i < _checks.Length; i++) + { + if (!_checks[i].CanRun(this, user, channel, out error)) + return false; + } + error = null; + return true; + } + + internal void SetRunFunc(Func func) + { + _runFunc = func; + } + internal void SetRunFunc(Action func) + { + _runFunc = TaskHelper.ToAsync(func); + } + internal Task Run(CommandEventArgs args) + { + var task = _runFunc(args); + if (task != null) + return task; + else + return TaskHelper.CompletedTask; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandBuilder.cs b/discord.net/src/Discord.Net.Commands/CommandBuilder.cs new file mode 100644 index 000000000..129dc24ab --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandBuilder.cs @@ -0,0 +1,163 @@ +using Discord.Commands.Permissions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + //TODO: Make this more friendly and expose it to be extendable + public sealed class CommandBuilder + { + private readonly CommandService _service; + private readonly Command _command; + private readonly List _params; + private readonly List _checks; + private readonly List _aliases; + private readonly string _prefix; + private bool _allowRequiredParams, _areParamsClosed; + + public CommandService Service => _service; + + internal CommandBuilder(CommandService service, string text, string prefix = "", string category = "", IEnumerable initialChecks = null) + { + _service = service; + _prefix = prefix; + + _command = new Command(AppendPrefix(prefix, text)); + _command.Category = category; + + if (initialChecks != null) + _checks = new List(initialChecks); + else + _checks = new List(); + + _params = new List(); + _aliases = new List(); + + _allowRequiredParams = true; + _areParamsClosed = false; + } + + public CommandBuilder Alias(params string[] aliases) + { + _aliases.AddRange(aliases); + return this; + } + /*public CommandBuilder Category(string category) + { + _command.Category = category; + return this; + }*/ + public CommandBuilder Description(string description) + { + _command.Description = description; + return this; + } + public CommandBuilder Parameter(string name, ParameterType type = ParameterType.Required) + { + if (_areParamsClosed) + throw new Exception($"No parameters may be added after a {nameof(ParameterType.Multiple)} or {nameof(ParameterType.Unparsed)} parameter."); + if (!_allowRequiredParams && type == ParameterType.Required) + throw new Exception($"{nameof(ParameterType.Required)} parameters may not be added after an optional one"); + + _params.Add(new CommandParameter(name, type)); + + if (type == ParameterType.Optional) + _allowRequiredParams = false; + if (type == ParameterType.Multiple || type == ParameterType.Unparsed) + _areParamsClosed = true; + return this; + } + public CommandBuilder Hide() + { + _command.IsHidden = true; + return this; + } + public CommandBuilder AddCheck(IPermissionChecker check) + { + _checks.Add(check); + return this; + } + public CommandBuilder AddCheck(Func checkFunc, string errorMsg = null) + { + _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); + return this; + } + + public void Do(Func func) + { + _command.SetRunFunc(func); + Build(); + } + public void Do(Action func) + { + _command.SetRunFunc(func); + Build(); + } + private void Build() + { + _command.SetParameters(_params.ToArray()); + _command.SetChecks(_checks.ToArray()); + _command.SetAliases(_aliases.Select(x => AppendPrefix(_prefix, x)).ToArray()); + _service.AddCommand(_command); + } + + internal static string AppendPrefix(string prefix, string cmd) + { + if (cmd != "") + { + if (prefix != "") + return prefix + ' ' + cmd; + else + return cmd; + } + else + return prefix; + } + } + public class CommandGroupBuilder + { + private readonly CommandService _service; + private readonly string _prefix; + private readonly List _checks; + private string _category; + + public CommandService Service => _service; + + internal CommandGroupBuilder(CommandService service, string prefix = "", string category = null, IEnumerable initialChecks = null) + { + _service = service; + _prefix = prefix; + _category = category; + if (initialChecks != null) + _checks = new List(initialChecks); + else + _checks = new List(); + } + + public CommandGroupBuilder Category(string category) + { + _category = category; + return this; + } + public void AddCheck(IPermissionChecker checker) + { + _checks.Add(checker); + } + public void AddCheck(Func checkFunc, string errorMsg = null) + { + _checks.Add(new GenericPermissionChecker(checkFunc, errorMsg)); + } + + public CommandGroupBuilder CreateGroup(string cmd, Action config) + { + config(new CommandGroupBuilder(_service, CommandBuilder.AppendPrefix(_prefix, cmd), _category, _checks)); + return this; + } + public CommandBuilder CreateCommand() + => CreateCommand(""); + public CommandBuilder CreateCommand(string cmd) + => new CommandBuilder(_service, cmd, _prefix, _category, _checks); + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandErrorEventArgs.cs b/discord.net/src/Discord.Net.Commands/CommandErrorEventArgs.cs new file mode 100644 index 000000000..5f47c6d7c --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandErrorEventArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace Discord.Commands +{ + public enum CommandErrorType { Exception, UnknownCommand, BadPermissions, BadArgCount, InvalidInput } + public class CommandErrorEventArgs : CommandEventArgs + { + public CommandErrorType ErrorType { get; } + public Exception Exception { get; } + + public CommandErrorEventArgs(CommandErrorType errorType, CommandEventArgs baseArgs, Exception ex) + : base(baseArgs.Message, baseArgs.Command, baseArgs.Args) + { + Exception = ex; + ErrorType = errorType; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandEventArgs.cs b/discord.net/src/Discord.Net.Commands/CommandEventArgs.cs new file mode 100644 index 000000000..818f5fa23 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandEventArgs.cs @@ -0,0 +1,27 @@ +using System; + +namespace Discord.Commands +{ + public class CommandEventArgs : EventArgs + { + private readonly string[] _args; + + public Message Message { get; } + public Command Command { get; } + + public User User => Message.User; + public Channel Channel => Message.Channel; + public Server Server => Message.Channel.Server; + + public CommandEventArgs(Message message, Command command, string[] args) + { + Message = message; + Command = command; + _args = args; + } + + public string[] Args => _args; + public string GetArg(int index) => _args[index]; + public string GetArg(string name) => _args[Command[name].Id]; + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandExtensions.cs b/discord.net/src/Discord.Net.Commands/CommandExtensions.cs new file mode 100644 index 000000000..c57cf099f --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandExtensions.cs @@ -0,0 +1,20 @@ +using System; + +namespace Discord.Commands +{ + public static class CommandExtensions + { + public static DiscordClient UsingCommands(this DiscordClient client, CommandServiceConfig config = null) + { + client.AddService(new CommandService(config)); + return client; + } + public static DiscordClient UsingCommands(this DiscordClient client, Action configFunc = null) + { + var builder = new CommandServiceConfigBuilder(); + configFunc(builder); + client.AddService(new CommandService(builder)); + return client; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandMap.cs b/discord.net/src/Discord.Net.Commands/CommandMap.cs new file mode 100644 index 000000000..98decd833 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandMap.cs @@ -0,0 +1,141 @@ +using System.Collections.Generic; + +namespace Discord.Commands +{ + //Represents either a single function, command group, or both + internal class CommandMap + { + private readonly CommandMap _parent; + private readonly string _name, _fullName; + + private readonly List _commands; + private readonly Dictionary _items; + private bool _isVisible, _hasNonAliases, _hasSubGroups; + + public string Name => _name; + public string FullName => _fullName; + public bool IsVisible => _isVisible; + public bool HasNonAliases => _hasNonAliases; + public bool HasSubGroups => _hasSubGroups; + public IEnumerable Commands => _commands; + public IEnumerable SubGroups => _items.Values; + + public CommandMap() + { + _items = new Dictionary(); + _commands = new List(); + _isVisible = false; + _hasNonAliases = false; + _hasSubGroups = false; + } + public CommandMap(CommandMap parent, string name, string fullName) + : this() + { + _parent = parent; + _name = name; + _fullName = fullName; + } + + public CommandMap GetItem(string text) + { + return GetItem(0, text.Split(' ')); + } + public CommandMap GetItem(int index, string[] parts) + { + if (index != parts.Length) + { + string nextPart = parts[index]; + CommandMap nextGroup; + if (_items.TryGetValue(nextPart.ToLowerInvariant(), out nextGroup)) + return nextGroup.GetItem(index + 1, parts); + else + return null; + } + return this; + } + + public IEnumerable GetCommands() + { + if (_commands.Count > 0) + return _commands; + else if (_parent != null) + return _parent.GetCommands(); + else + return null; + } + public IEnumerable GetCommands(string text) + { + return GetCommands(0, text.Split(' ')); + } + public IEnumerable GetCommands(int index, string[] parts) + { + if (index != parts.Length) + { + string nextPart = parts[index]; + CommandMap nextGroup; + if (_items.TryGetValue(nextPart.ToLowerInvariant(), out nextGroup)) + { + var cmd = nextGroup.GetCommands(index + 1, parts); + if (cmd != null) + return cmd; + } + } + + if (_commands != null) + return _commands; + return null; + } + + public void AddCommand(string text, Command command, bool isAlias) + { + AddCommand(0, text.Split(' '), command, isAlias); + } + private void AddCommand(int index, string[] parts, Command command, bool isAlias) + { + if (!command.IsHidden) + _isVisible = true; + + if (index != parts.Length) + { + CommandMap nextGroup; + string name = parts[index].ToLowerInvariant(); + string fullName = string.Join(" ", parts, 0, index + 1); + if (!_items.TryGetValue(name, out nextGroup)) + { + nextGroup = new CommandMap(this, name, fullName); + _items.Add(name, nextGroup); + _hasSubGroups = true; + } + nextGroup.AddCommand(index + 1, parts, command, isAlias); + } + else + { + _commands.Add(command); + if (!isAlias) + _hasNonAliases = true; + } + } + + public bool CanRun(User user, Channel channel, out string error) + { + error = null; + if (_commands.Count > 0) + { + foreach (var cmd in _commands) + { + if (cmd.CanRun(user, channel, out error)) + return true; + } + } + if (_items.Count > 0) + { + foreach (var item in _items) + { + if (item.Value.CanRun(user, channel, out error)) + return true; + } + } + return false; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandParameter.cs b/discord.net/src/Discord.Net.Commands/CommandParameter.cs new file mode 100644 index 000000000..d7361bef4 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandParameter.cs @@ -0,0 +1,26 @@ +namespace Discord.Commands +{ + public enum ParameterType + { + /// Catches a single required parameter. + Required, + /// Catches a single optional parameter. + Optional, + /// Catches a zero or more optional parameters. + Multiple, + /// Catches all remaining text as a single optional parameter. + Unparsed + } + public class CommandParameter + { + public string Name { get; } + public int Id { get; internal set; } + public ParameterType Type { get; } + + internal CommandParameter(string name, ParameterType type) + { + Name = name; + Type = type; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandParser.cs b/discord.net/src/Discord.Net.Commands/CommandParser.cs new file mode 100644 index 000000000..cfdbe6903 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandParser.cs @@ -0,0 +1,188 @@ +using System.Collections.Generic; + +namespace Discord.Commands +{ + internal static class CommandParser + { + private enum ParserPart + { + None, + Parameter, + QuotedParameter, + DoubleQuotedParameter + } + + public static bool ParseCommand(string input, CommandMap map, out IEnumerable commands, out int endPos) + { + int startPosition = 0; + int endPosition = 0; + int inputLength = input.Length; + bool isEscaped = false; + commands = null; + endPos = 0; + + if (input == "") + return false; + + while (endPosition < inputLength) + { + char currentChar = input[endPosition++]; + if (isEscaped) + isEscaped = false; + else if (currentChar == '\\') + isEscaped = true; + + bool isWhitespace = IsWhiteSpace(currentChar); + if ((!isEscaped && isWhitespace) || endPosition >= inputLength) + { + int length = (isWhitespace ? endPosition - 1 : endPosition) - startPosition; + string temp = input.Substring(startPosition, length); + if (temp == "") + startPosition = endPosition; + else + { + var newMap = map.GetItem(temp); + if (newMap != null) + { + map = newMap; + endPos = endPosition; + } + else + break; + startPosition = endPosition; + } + } + } + commands = map.GetCommands(); //Work our way backwards to find a command that matches our input + return commands != null; + } + private static bool IsWhiteSpace(char c) => c == ' ' || c == '\n' || c == '\r' || c == '\t'; + + //TODO: Check support for escaping + public static CommandErrorType? ParseArgs(string input, int startPos, Command command, out string[] args) + { + ParserPart currentPart = ParserPart.None; + int startPosition = startPos; + int endPosition = startPos; + int inputLength = input.Length; + bool isEscaped = false; + + var expectedArgs = command._parameters; + List argList = new List(); + CommandParameter parameter = null; + + args = null; + + if (input == "") + return CommandErrorType.InvalidInput; + + while (endPosition < inputLength) + { + if (startPosition == endPosition && (parameter == null || parameter.Type != ParameterType.Multiple)) //Is first char of a new arg + { + if (argList.Count >= expectedArgs.Length) + return CommandErrorType.BadArgCount; //Too many args + parameter = expectedArgs[argList.Count]; + if (parameter.Type == ParameterType.Unparsed) + { + argList.Add(input.Substring(startPosition)); + break; + } + } + + char currentChar = input[endPosition++]; + if (isEscaped) + isEscaped = false; + else if (currentChar == '\\') + isEscaped = true; + + bool isWhitespace = IsWhiteSpace(currentChar); + if (endPosition == startPosition + 1 && isWhitespace) //Has no text yet, and is another whitespace + { + startPosition = endPosition; + continue; + } + + switch (currentPart) + { + case ParserPart.None: + if ((!isEscaped && currentChar == '\"')) + { + currentPart = ParserPart.DoubleQuotedParameter; + startPosition = endPosition; + } + else if ((!isEscaped && currentChar == '\'')) + { + currentPart = ParserPart.QuotedParameter; + startPosition = endPosition; + } + else if ((!isEscaped && isWhitespace) || endPosition >= inputLength) + { + int length = (isWhitespace ? endPosition - 1 : endPosition) - startPosition; + if (length == 0) + startPosition = endPosition; + else + { + string temp = input.Substring(startPosition, length); + argList.Add(temp); + currentPart = ParserPart.None; + startPosition = endPosition; + } + } + break; + case ParserPart.QuotedParameter: + if ((!isEscaped && currentChar == '\'')) + { + string temp = input.Substring(startPosition, endPosition - startPosition - 1); + argList.Add(temp); + currentPart = ParserPart.None; + startPosition = endPosition; + } + else if (endPosition >= inputLength) + return CommandErrorType.InvalidInput; + break; + case ParserPart.DoubleQuotedParameter: + if ((!isEscaped && currentChar == '\"')) + { + string temp = input.Substring(startPosition, endPosition - startPosition - 1); + argList.Add(temp); + currentPart = ParserPart.None; + startPosition = endPosition; + } + else if (endPosition >= inputLength) + return CommandErrorType.InvalidInput; + break; + } + } + + //Unclosed quotes + if (currentPart == ParserPart.QuotedParameter || + currentPart == ParserPart.DoubleQuotedParameter) + return CommandErrorType.InvalidInput; + + //Too few args + for (int i = argList.Count; i < expectedArgs.Length; i++) + { + var param = expectedArgs[i]; + switch (param.Type) + { + case ParameterType.Required: + return CommandErrorType.BadArgCount; + case ParameterType.Optional: + case ParameterType.Unparsed: + argList.Add(""); + break; + } + } + + /*if (argList.Count > expectedArgs.Length) + { + if (expectedArgs.Length == 0 || expectedArgs[expectedArgs.Length - 1].Type != ParameterType.Multiple) + return CommandErrorType.BadArgCount; + }*/ + + args = argList.ToArray(); + return null; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandService.cs b/discord.net/src/Discord.Net.Commands/CommandService.cs new file mode 100644 index 000000000..4b3a67edf --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandService.cs @@ -0,0 +1,388 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Discord.Commands +{ + public partial class CommandService : IService + { + private readonly List _allCommands; + private readonly Dictionary _categories; + private readonly CommandMap _map; //Command map stores all commands by their input text, used for fast resolving and parsing + + public CommandServiceConfig Config { get; } + public CommandGroupBuilder Root { get; } + public DiscordClient Client { get; private set; } + + //AllCommands store a flattened collection of all commands + public IEnumerable AllCommands => _allCommands; + //Groups store all commands by their module, used for more informative help + internal IEnumerable Categories => _categories.Values; + + public event EventHandler CommandExecuted = delegate { }; + public event EventHandler CommandErrored = delegate { }; + + private void OnCommand(CommandEventArgs args) + => CommandExecuted(this, args); + private void OnCommandError(CommandErrorType errorType, CommandEventArgs args, Exception ex = null) + => CommandErrored(this, new CommandErrorEventArgs(errorType, args, ex)); + + public CommandService() + : this(new CommandServiceConfigBuilder()) + { + } + public CommandService(CommandServiceConfigBuilder builder) + : this(builder.Build()) + { + if (builder.ExecuteHandler != null) + CommandExecuted += builder.ExecuteHandler; + if (builder.ErrorHandler != null) + CommandErrored += builder.ErrorHandler; + } + public CommandService(CommandServiceConfig config) + { + Config = config; + + _allCommands = new List(); + _map = new CommandMap(); + _categories = new Dictionary(); + Root = new CommandGroupBuilder(this); + } + + void IService.Install(DiscordClient client) + { + Client = client; + + if (Config.HelpMode != HelpMode.Disabled) + { + CreateCommand("help") + .Parameter("command", ParameterType.Multiple) + .Hide() + .Description("Returns information about commands.") + .Do(async e => + { + Channel replyChannel = Config.HelpMode == HelpMode.Public ? e.Channel : await e.User.CreatePMChannel().ConfigureAwait(false); + if (e.Args.Length > 0) //Show command help + { + var map = _map.GetItem(string.Join(" ", e.Args)); + if (map != null) + await ShowCommandHelp(map, e.User, e.Channel, replyChannel).ConfigureAwait(false); + else + await replyChannel.SendMessage("Unable to display help: Unknown command.").ConfigureAwait(false); + } + else //Show general help + await ShowGeneralHelp(e.User, e.Channel, replyChannel).ConfigureAwait(false); + }); + } + + client.MessageReceived += async (s, e) => + { + if (_allCommands.Count == 0) return; + + if (Config.IsSelfBot) + { + if (e.Message.User == null || e.Message.User.Id != Client.CurrentUser.Id) return; // Will only listen to Self + } + else + if (e.Message.User == null || e.Message.User.Id == Client.CurrentUser.Id) return; // Normal expected behavior for bots + + string msg = e.Message.RawText; + if (msg.Length == 0) return; + + string cmdMsg = null; + + //Check for command char + if (Config.PrefixChar.HasValue) + { + if (msg[0] == Config.PrefixChar.Value) + cmdMsg = msg.Substring(1); + } + + //Check for mention + if (cmdMsg == null && Config.AllowMentionPrefix) + { + string mention = client.CurrentUser.Mention; + if (msg.StartsWith(mention) && msg.Length > mention.Length) + cmdMsg = msg.Substring(mention.Length + 1); + else + { + mention = $"@{client.CurrentUser.Name}"; + if (msg.StartsWith(mention) && msg.Length > mention.Length) + cmdMsg = msg.Substring(mention.Length + 1); + } + + string mention2 = client.CurrentUser.NicknameMention; + if (mention2 != null) + { + if (msg.StartsWith(mention2) && msg.Length > mention2.Length) + cmdMsg = msg.Substring(mention2.Length + 1); + else + { + mention2 = $"@{client.CurrentUser.Name}"; + if (msg.StartsWith(mention2) && msg.Length > mention2.Length) + cmdMsg = msg.Substring(mention2.Length + 1); + } + } + } + + //Check using custom activator + if (cmdMsg == null && Config.CustomPrefixHandler != null) + { + int index = Config.CustomPrefixHandler(e.Message); + if (index >= 0) + cmdMsg = msg.Substring(index); + } + + if (cmdMsg == null) return; + + //Parse command + IEnumerable commands; + int argPos; + CommandParser.ParseCommand(cmdMsg, _map, out commands, out argPos); + if (commands == null) + { + CommandEventArgs errorArgs = new CommandEventArgs(e.Message, null, null); + OnCommandError(CommandErrorType.UnknownCommand, errorArgs); + return; + } + else + { + foreach (var command in commands) + { + //Parse arguments + string[] args; + var error = CommandParser.ParseArgs(cmdMsg, argPos, command, out args); + if (error != null) + { + if (error == CommandErrorType.BadArgCount) + continue; + else + { + var errorArgs = new CommandEventArgs(e.Message, command, null); + OnCommandError(error.Value, errorArgs); + return; + } + } + + var eventArgs = new CommandEventArgs(e.Message, command, args); + + // Check permissions + string errorText; + if (!command.CanRun(eventArgs.User, eventArgs.Channel, out errorText)) + { + OnCommandError(CommandErrorType.BadPermissions, eventArgs, errorText != null ? new Exception(errorText) : null); + return; + } + + // Run the command + try + { + OnCommand(eventArgs); + await command.Run(eventArgs).ConfigureAwait(false); + } + catch (Exception ex) + { + OnCommandError(CommandErrorType.Exception, eventArgs, ex); + } + return; + } + var errorArgs2 = new CommandEventArgs(e.Message, null, null); + OnCommandError(CommandErrorType.BadArgCount, errorArgs2); + } + }; + } + + public Task ShowGeneralHelp(User user, Channel channel, Channel replyChannel = null) + { + StringBuilder output = new StringBuilder(); + bool isFirstCategory = true; + foreach (var category in _categories) + { + bool isFirstItem = true; + foreach (var group in category.Value.SubGroups) + { + string error; + if (group.IsVisible && (group.HasSubGroups || group.HasNonAliases) && group.CanRun(user, channel, out error)) + { + if (isFirstItem) + { + isFirstItem = false; + //This is called for the first item in each category. If we never get here, we dont bother writing the header for a category type (since it's empty) + if (isFirstCategory) + { + isFirstCategory = false; + //Called for the first non-empty category + output.AppendLine("These are the commands you can use:"); + } + else + output.AppendLine(); + if (category.Key != "") + { + output.Append(Format.Bold(category.Key)); + output.Append(": "); + } + } + else + output.Append(", "); + output.Append('`'); + output.Append(group.Name); + if (group.HasSubGroups) + output.Append("*"); + output.Append('`'); + } + } + } + + if (output.Length == 0) + output.Append("There are no commands you have permission to run."); + else + { + output.Append("\n\n"); + + //TODO: Should prefix be stated in the help message or not? + /*StringBuilder builder = new StringBuilder(); + if (Config.PrefixChar != null) + { + builder.Append('`'); + builder.Append(Config.PrefixChar.Value); + builder.Append('`'); + } + if (Config.AllowMentionPrefix) + { + if (builder.Length > 0) + builder.Append(" or "); + builder.Append(Client.CurrentUser.Mention); + } + if (builder.Length > 0) + output.AppendLine($"Start your message with {builder.ToString()} to run a command.");*/ + output.AppendLine($"Run `help ` for more information."); + } + + return (replyChannel ?? channel).SendMessage(output.ToString()); + } + + private Task ShowCommandHelp(CommandMap map, User user, Channel channel, Channel replyChannel = null) + { + StringBuilder output = new StringBuilder(); + + IEnumerable cmds = map.Commands; + bool isFirstCmd = true; + string error; + if (cmds.Any()) + { + foreach (var cmd in cmds) + { + if (!cmd.CanRun(user, channel, out error)) { } + //output.AppendLine(error ?? DefaultPermissionError); + else + { + if (isFirstCmd) + isFirstCmd = false; + else + output.AppendLine(); + ShowCommandHelpInternal(cmd, user, channel, output); + } + } + } + else + { + output.Append('`'); + output.Append(map.FullName); + output.Append("`\n"); + } + + bool isFirstSubCmd = true; + foreach (var subCmd in map.SubGroups.Where(x => x.CanRun(user, channel, out error) && x.IsVisible)) + { + if (isFirstSubCmd) + { + isFirstSubCmd = false; + output.AppendLine("Sub Commands: "); + } + else + output.Append(", "); + output.Append('`'); + output.Append(subCmd.Name); + if (subCmd.SubGroups.Any()) + output.Append("*"); + output.Append('`'); + } + + if (isFirstCmd && isFirstSubCmd) //Had no commands and no subcommands + { + output.Clear(); + output.AppendLine("There are no commands you have permission to run."); + } + + return (replyChannel ?? channel).SendMessage(output.ToString()); + } + public Task ShowCommandHelp(Command command, User user, Channel channel, Channel replyChannel = null) + { + StringBuilder output = new StringBuilder(); + string error; + if (!command.CanRun(user, channel, out error)) + output.AppendLine(error ?? "You do not have permission to access this command."); + else + ShowCommandHelpInternal(command, user, channel, output); + return (replyChannel ?? channel).SendMessage(output.ToString()); + } + private void ShowCommandHelpInternal(Command command, User user, Channel channel, StringBuilder output) + { + output.Append('`'); + output.Append(command.Text); + foreach (var param in command.Parameters) + { + switch (param.Type) + { + case ParameterType.Required: + output.Append($" <{param.Name}>"); + break; + case ParameterType.Optional: + output.Append($" [{param.Name}]"); + break; + case ParameterType.Multiple: + output.Append($" [{param.Name}...]"); + break; + case ParameterType.Unparsed: + output.Append($" [-]"); + break; + } + } + output.AppendLine("`"); + output.AppendLine($"{command.Description ?? "No description."}"); + + if (command.Aliases.Any()) + output.AppendLine($"Aliases: `" + string.Join("`, `", command.Aliases) + '`'); + } + + public void CreateGroup(string cmd, Action config = null) => Root.CreateGroup(cmd, config); + public CommandBuilder CreateCommand(string cmd) => Root.CreateCommand(cmd); + + internal void AddCommand(Command command) + { + _allCommands.Add(command); + + //Get category + CommandMap category; + string categoryName = command.Category ?? ""; + if (!_categories.TryGetValue(categoryName, out category)) + { + category = new CommandMap(); + _categories.Add(categoryName, category); + } + + //Add main command + category.AddCommand(command.Text, command, false); + _map.AddCommand(command.Text, command, false); + + //Add aliases + foreach (var alias in command.Aliases) + { + category.AddCommand(alias, command, true); + _map.AddCommand(alias, command, true); + } + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/CommandServiceConfig.cs b/discord.net/src/Discord.Net.Commands/CommandServiceConfig.cs new file mode 100644 index 000000000..c772b273d --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -0,0 +1,53 @@ +using System; + +namespace Discord.Commands +{ + public class CommandServiceConfigBuilder + { + /// Gets or sets the prefix character used to trigger commands, if ActivationMode has the Char flag set. + public char? PrefixChar { get; set; } = null; + /// Gets or sets whether a message beginning with a mention to the logged-in user should be treated as a command. + public bool AllowMentionPrefix { get; set; } = true; + /// + /// Gets or sets a custom function used to detect messages that should be treated as commands. + /// This function should a positive one indicating the index of where the in the message's RawText the command begins, + /// and a negative value if the message should be ignored. + /// + public Func CustomPrefixHandler { get; set; } = null; + /// + /// Changing this to true makes the bot ignore all messages, except when the messages are from its own account. + /// This is desired behavior for "Self Bots" only, so unless this bot is being run under a normal user's account, leave it alone!! + /// + public bool IsSelfBot { get; set; } = false; + + /// Gets or sets whether a help function should be automatically generated. + public HelpMode HelpMode { get; set; } = HelpMode.Disabled; + + + /// Gets or sets a handler that is called on any successful command execution. + public EventHandler ExecuteHandler { get; set; } + /// Gets or sets a handler that is called on any error during command parsing or execution. + public EventHandler ErrorHandler { get; set; } + + public CommandServiceConfig Build() => new CommandServiceConfig(this); + } + public class CommandServiceConfig + { + public char? PrefixChar { get; } + public bool AllowMentionPrefix { get; } + public Func CustomPrefixHandler { get; } + public bool IsSelfBot { get; } + + /// Gets or sets whether a help function should be automatically generated. + public HelpMode HelpMode { get; set; } = HelpMode.Disabled; + + internal CommandServiceConfig(CommandServiceConfigBuilder builder) + { + PrefixChar = builder.PrefixChar; + AllowMentionPrefix = builder.AllowMentionPrefix; + CustomPrefixHandler = builder.CustomPrefixHandler; + HelpMode = builder.HelpMode; + IsSelfBot = builder.IsSelfBot; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Discord.Net.Commands.xproj b/discord.net/src/Discord.Net.Commands/Discord.Net.Commands.xproj new file mode 100644 index 000000000..b6a050836 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Discord.Net.Commands.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 19793545-ef89-48f4-8100-3ebaad0a9141 + Discord.Commands + ..\..\artifacts\obj\$(MSBuildProjectName) + .\bin\ + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Commands/HelpMode.cs b/discord.net/src/Discord.Net.Commands/HelpMode.cs new file mode 100644 index 000000000..272403f42 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/HelpMode.cs @@ -0,0 +1,12 @@ +namespace Discord.Commands +{ + public enum HelpMode + { + /// Disable the automatic help command. + Disabled, + /// Use the automatic help command and respond in the channel the command is used. + Public, + /// Use the automatic help command and respond in a private message. + Private + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs b/discord.net/src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs new file mode 100644 index 000000000..05e95ac64 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/GenericPermissionChecker.cs @@ -0,0 +1,22 @@ +using System; + +namespace Discord.Commands.Permissions +{ + internal class GenericPermissionChecker : IPermissionChecker + { + private readonly Func _checkFunc; + private readonly string _error; + + public GenericPermissionChecker(Func checkFunc, string error = null) + { + _checkFunc = checkFunc; + _error = error; + } + + public bool CanRun(Command command, User user, Channel channel, out string error) + { + error = _error; + return _checkFunc(command, user, channel); + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/IPermissionChecker.cs b/discord.net/src/Discord.Net.Commands/Permissions/IPermissionChecker.cs new file mode 100644 index 000000000..f400c3420 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/IPermissionChecker.cs @@ -0,0 +1,7 @@ +namespace Discord.Commands.Permissions +{ + public interface IPermissionChecker + { + bool CanRun(Command command, User user, Channel channel, out string error); + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs b/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs new file mode 100644 index 000000000..0092c4edf --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelChecker.cs @@ -0,0 +1,24 @@ +namespace Discord.Commands.Permissions.Levels +{ + public class PermissionLevelChecker : IPermissionChecker + { + private readonly PermissionLevelService _service; + private readonly int _minPermissions; + + public PermissionLevelService Service => _service; + public int MinPermissions => _minPermissions; + + internal PermissionLevelChecker(DiscordClient client, int minPermissions) + { + _service = client.GetService(true); + _minPermissions = minPermissions; + } + + public bool CanRun(Command command, User user, Channel channel, out string error) + { + error = null; //Use default error text. + int permissions = _service.GetPermissionLevel(user, channel); + return permissions >= _minPermissions; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs b/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs new file mode 100644 index 000000000..10f153215 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelExtensions.cs @@ -0,0 +1,29 @@ +using System; + +namespace Discord.Commands.Permissions.Levels +{ + public static class PermissionLevelExtensions + { + public static DiscordClient UsingPermissionLevels(this DiscordClient client, Func permissionResolver) + { + client.AddService(new PermissionLevelService(permissionResolver)); + return client; + } + + public static CommandBuilder MinPermissions(this CommandBuilder builder, int minPermissions) + { + builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); + return builder; + } + public static CommandGroupBuilder MinPermissions(this CommandGroupBuilder builder, int minPermissions) + { + builder.AddCheck(new PermissionLevelChecker(builder.Service.Client, minPermissions)); + return builder; + } + public static CommandService MinPermissions(this CommandService service, int minPermissions) + { + service.Root.AddCheck(new PermissionLevelChecker(service.Client, minPermissions)); + return service; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs b/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs new file mode 100644 index 000000000..6bab13b97 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Levels/PermissionLevelService.cs @@ -0,0 +1,23 @@ +using System; + +namespace Discord.Commands.Permissions.Levels +{ + public class PermissionLevelService : IService + { + private readonly Func _getPermissionsFunc; + + private DiscordClient _client; + public DiscordClient Client => _client; + + public PermissionLevelService(Func getPermissionsFunc) + { + _getPermissionsFunc = getPermissionsFunc; + } + + public void Install(DiscordClient client) + { + _client = client; + } + public int GetPermissionLevel(User user, Channel channel) => _getPermissionsFunc(user, channel); + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs new file mode 100644 index 000000000..23c48cba9 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistChecker.cs @@ -0,0 +1,18 @@ +namespace Discord.Commands.Permissions.Userlist +{ + public class BlacklistChecker : IPermissionChecker + { + private readonly BlacklistService _service; + + internal BlacklistChecker(DiscordClient client) + { + _service = client.GetService(true); + } + + public bool CanRun(Command command, User user, Channel channel, out string error) + { + error = null; //Use default error text. + return _service.CanRun(user); + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs new file mode 100644 index 000000000..1ff51ee58 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistExtensions.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace Discord.Commands.Permissions.Userlist +{ + public static class BlacklistExtensions + { + public static DiscordClient UsingGlobalBlacklist(this DiscordClient client, params ulong[] initialUserIds) + { + client.AddService(new BlacklistService(initialUserIds)); + return client; + } + + public static CommandBuilder UseGlobalBlacklist(this CommandBuilder builder) + { + builder.AddCheck(new BlacklistChecker(builder.Service.Client)); + return builder; + } + public static CommandGroupBuilder UseGlobalBlacklist(this CommandGroupBuilder builder) + { + builder.AddCheck(new BlacklistChecker(builder.Service.Client)); + return builder; + } + public static CommandService UseGlobalBlacklist(this CommandService service) + { + service.Root.AddCheck(new BlacklistChecker(service.Client)); + return service; + } + + public static IEnumerable GetBlacklistedUserIds(this DiscordClient client) + => client.GetService().UserIds; + public static void BlacklistUser(this DiscordClient client, User user) + { + client.GetService().Add(user.Id); + } + public static void BlacklistUser(this DiscordClient client, ulong userId) + { + client.GetService().Add(userId); + } + public static void UnBlacklistUser(this DiscordClient client, User user) + { + client.GetService().Remove(user.Id); + } + public static void UnBlacklistUser(this DiscordClient client, ulong userId) + { + client.GetService().Remove(userId); + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs new file mode 100644 index 000000000..ced4c3fdc --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/BlacklistService.cs @@ -0,0 +1,13 @@ +namespace Discord.Commands.Permissions.Userlist +{ + public class BlacklistService : UserlistService + { + public BlacklistService(params ulong[] initialList) + : base(initialList) + { + } + + public bool CanRun(User user) + => !_userList.ContainsKey(user.Id); + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs new file mode 100644 index 000000000..da8264312 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/UserlistService.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Discord.Commands.Permissions.Userlist +{ + public class UserlistService : IService + { + protected readonly ConcurrentDictionary _userList; + private DiscordClient _client; + + public DiscordClient Client => _client; + public IEnumerable UserIds => _userList.Select(x => x.Key); + + public UserlistService(params ulong[] initialUserIds) + { + _userList = new ConcurrentDictionary(); + for (int i = 0; i < initialUserIds.Length; i++) + _userList.TryAdd(initialUserIds[i], true); + } + + public void Add(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + _userList[user.Id] = true; + } + public void Add(ulong userId) + { + _userList[userId] = true; + } + + public bool Remove(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + bool ignored; + return _userList.TryRemove(user.Id, out ignored); + } + public bool Remove(ulong userId) + { + bool ignored; + return _userList.TryRemove(userId, out ignored); + } + + void IService.Install(DiscordClient client) + { + _client = client; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs new file mode 100644 index 000000000..fa441644f --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistChecker.cs @@ -0,0 +1,18 @@ +namespace Discord.Commands.Permissions.Userlist +{ + public class WhitelistChecker : IPermissionChecker + { + private readonly WhitelistService _service; + + internal WhitelistChecker(DiscordClient client) + { + _service = client.GetService(true); + } + + public bool CanRun(Command command, User user, Channel channel, out string error) + { + error = null; //Use default error text. + return _service.CanRun(user); + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs new file mode 100644 index 000000000..391668298 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistExtensions.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; + +namespace Discord.Commands.Permissions.Userlist +{ + public static class WhitelistExtensions + { + public static DiscordClient UsingGlobalWhitelist(this DiscordClient client, params ulong[] initialUserIds) + { + client.AddService(new WhitelistService(initialUserIds)); + return client; + } + + public static CommandBuilder UseGlobalWhitelist(this CommandBuilder builder) + { + builder.AddCheck(new WhitelistChecker(builder.Service.Client)); + return builder; + } + public static CommandGroupBuilder UseGlobalWhitelist(this CommandGroupBuilder builder) + { + builder.AddCheck(new WhitelistChecker(builder.Service.Client)); + return builder; + } + public static CommandService UseGlobalWhitelist(this CommandService service) + { + service.Root.AddCheck(new BlacklistChecker(service.Client)); + return service; + } + + public static IEnumerable GetWhitelistedUserIds(this DiscordClient client) + => client.GetService().UserIds; + public static void WhitelistUser(this DiscordClient client, User user) + { + client.GetService().Add(user.Id); + } + public static void WhitelistUser(this DiscordClient client, ulong userId) + { + client.GetService().Add(userId); + } + public static void UnWhitelistUser(this DiscordClient client, User user) + { + client.GetService().Remove(user.Id); + } + public static void RemoveFromWhitelist(this DiscordClient client, ulong userId) + { + client.GetService().Remove(userId); + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs new file mode 100644 index 000000000..ae25d3fd1 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Userlist/WhitelistService.cs @@ -0,0 +1,13 @@ +namespace Discord.Commands.Permissions.Userlist +{ + public class WhitelistService : UserlistService + { + public WhitelistService(params ulong[] initialList) + : base(initialList) + { + } + + public bool CanRun(User user) + => _userList.ContainsKey(user.Id); + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs b/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs new file mode 100644 index 000000000..dd336042d --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PrivateChecker.cs @@ -0,0 +1,21 @@ +namespace Discord.Commands.Permissions.Visibility +{ + public class PrivateChecker : IPermissionChecker + { + internal PrivateChecker() { } + + public bool CanRun(Command command, User user, Channel channel, out string error) + { + if (user.Server != null) + { + error = "This command may only be run in a private chat."; + return false; + } + else + { + error = null; + return true; + } + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs b/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs new file mode 100644 index 000000000..cb3579983 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PrivateExtensions.cs @@ -0,0 +1,21 @@ +namespace Discord.Commands.Permissions.Visibility +{ + public static class PrivateExtensions + { + public static CommandBuilder PrivateOnly(this CommandBuilder builder) + { + builder.AddCheck(new PrivateChecker()); + return builder; + } + public static CommandGroupBuilder PrivateOnly(this CommandGroupBuilder builder) + { + builder.AddCheck(new PrivateChecker()); + return builder; + } + public static CommandService PrivateOnly(this CommandService service) + { + service.Root.AddCheck(new PrivateChecker()); + return service; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs b/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs new file mode 100644 index 000000000..9e70b647b --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PublicChecker.cs @@ -0,0 +1,21 @@ +namespace Discord.Commands.Permissions.Visibility +{ + public class PublicChecker : IPermissionChecker + { + internal PublicChecker() { } + + public bool CanRun(Command command, User user, Channel channel, out string error) + { + if (user.Server == null) + { + error = "This command can't be run in a private chat."; + return false; + } + else + { + error = null; + return true; + } + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs b/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs new file mode 100644 index 000000000..8cd78a4fe --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/Permissions/Visibility/PublicExtensions.cs @@ -0,0 +1,21 @@ +namespace Discord.Commands.Permissions.Visibility +{ + public static class PublicExtensions + { + public static CommandBuilder PublicOnly(this CommandBuilder builder) + { + builder.AddCheck(new PublicChecker()); + return builder; + } + public static CommandGroupBuilder PublicOnly(this CommandGroupBuilder builder) + { + builder.AddCheck(new PublicChecker()); + return builder; + } + public static CommandService PublicOnly(this CommandService service) + { + service.Root.AddCheck(new PublicChecker()); + return service; + } + } +} diff --git a/discord.net/src/Discord.Net.Commands/project.json b/discord.net/src/Discord.Net.Commands/project.json new file mode 100644 index 000000000..af616c0d8 --- /dev/null +++ b/discord.net/src/Discord.Net.Commands/project.json @@ -0,0 +1,38 @@ +{ + "version": "0.9.2", + "description": "A Discord.Net extension adding basic command support.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "projectUrl": "https://github.com/RogueException/Discord.Net", + "licenseUrl": "http://opensource.org/licenses/MIT", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "buildOptions": { + "compile": [ "**/*.cs", "../Discord.Net.Shared/*.cs" ], + "warningsAsErrors": true + }, + + "dependencies": { + "Discord.Net": "0.9.2" + }, + + "frameworks": { + "netstandard1.3": { + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027" + }, + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + }, + "net45": {} + } +} diff --git a/discord.net/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj b/discord.net/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj new file mode 100644 index 000000000..cab137c25 --- /dev/null +++ b/discord.net/src/Discord.Net.Modules.Net45/Discord.Net.Modules.csproj @@ -0,0 +1,89 @@ + + + + + Debug + AnyCPU + {3091164F-66AE-4543-A63D-167C1116241D} + Library + Properties + Discord.Modules + Discord.Net.Modules + 512 + v4.5 + False + + + + true + full + false + bin\Debug\ + TRACE;DEBUG;NET45 + prompt + 4 + 6 + true + true + + + pdbonly + true + bin\Release\ + TRACE;NET45 + prompt + 4 + true + 6 + true + + + + + + + IModule.cs + + + ModuleChecker.cs + + + ModuleExtensions.cs + + + ModuleFilter.cs + + + ModuleManager.cs + + + ModuleService.cs + + + + + + {1b5603b4-6f8f-4289-b945-7baae523d740} + Discord.Net.Commands + + + {8d71a857-879a-4a10-859e-5ff824ed6688} + Discord.Net + + + + + + project.json + + + + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Modules.Net45/Properties/AssemblyInfo.cs b/discord.net/src/Discord.Net.Modules.Net45/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..35d8fa220 --- /dev/null +++ b/discord.net/src/Discord.Net.Modules.Net45/Properties/AssemblyInfo.cs @@ -0,0 +1,18 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Discord.Net.Modules")] +[assembly: AssemblyDescription("A Discord.Net extension adding basic plugin support.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("RogueException")] +[assembly: AssemblyProduct("Discord.Net.Modules")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] +[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] + +[assembly: AssemblyVersion("0.9.2.0")] +[assembly: AssemblyFileVersion("0.9.2.0")] + diff --git a/discord.net/src/Discord.Net.Modules.Net45/project.json b/discord.net/src/Discord.Net.Modules.Net45/project.json new file mode 100644 index 000000000..a174430f0 --- /dev/null +++ b/discord.net/src/Discord.Net.Modules.Net45/project.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "Nito.AsyncEx": "3.0.1" + }, + "frameworks": { + "net45": { } + }, + "runtimes": { + "win": { }, + "win-x86": { }, + "win-x64": { } + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Modules/Discord.Net.Modules.xproj b/discord.net/src/Discord.Net.Modules/Discord.Net.Modules.xproj new file mode 100644 index 000000000..525fdd9e5 --- /dev/null +++ b/discord.net/src/Discord.Net.Modules/Discord.Net.Modules.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 01584e8a-78da-486f-9ef9-a894e435841b + Discord.Modules + ..\..\artifacts\obj\$(MSBuildProjectName) + .\bin\ + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Modules/IModule.cs b/discord.net/src/Discord.Net.Modules/IModule.cs new file mode 100644 index 000000000..48a594eef --- /dev/null +++ b/discord.net/src/Discord.Net.Modules/IModule.cs @@ -0,0 +1,7 @@ +namespace Discord.Modules +{ + public interface IModule + { + void Install(ModuleManager manager); + } +} diff --git a/discord.net/src/Discord.Net.Modules/ModuleChecker.cs b/discord.net/src/Discord.Net.Modules/ModuleChecker.cs new file mode 100644 index 000000000..7b54c8a2d --- /dev/null +++ b/discord.net/src/Discord.Net.Modules/ModuleChecker.cs @@ -0,0 +1,31 @@ +using Discord.Commands; +using Discord.Commands.Permissions; + +namespace Discord.Modules +{ + public class ModuleChecker : IPermissionChecker + { + private readonly ModuleManager _manager; + private readonly ModuleFilter _filterType; + + internal ModuleChecker(ModuleManager manager) + { + _manager = manager; + _filterType = manager.FilterType; + } + + public bool CanRun(Command command, User user, Channel channel, out string error) + { + if (_filterType == ModuleFilter.None || _filterType == ModuleFilter.AlwaysAllowPrivate || _manager.HasChannel(channel)) + { + error = null; + return true; + } + else + { + error = "This module is currently disabled."; + return false; + } + } + } +} diff --git a/discord.net/src/Discord.Net.Modules/ModuleExtensions.cs b/discord.net/src/Discord.Net.Modules/ModuleExtensions.cs new file mode 100644 index 000000000..a96517c06 --- /dev/null +++ b/discord.net/src/Discord.Net.Modules/ModuleExtensions.cs @@ -0,0 +1,29 @@ +namespace Discord.Modules +{ + public static class ModuleExtensions + { + public static DiscordClient UsingModules(this DiscordClient client) + { + client.AddService(new ModuleService()); + return client; + } + + public static void AddModule(this DiscordClient client, IModule instance, string name = null, ModuleFilter filter = ModuleFilter.None) + { + client.GetService().Add(instance, name, filter); + } + public static void AddModule(this DiscordClient client, string name = null, ModuleFilter filter = ModuleFilter.None) + where T : class, IModule, new() + { + client.GetService().Add(name, filter); + } + public static void AddModule(this DiscordClient client, T instance, string name = null, ModuleFilter filter = ModuleFilter.None) + where T : class, IModule + { + client.GetService().Add(instance, name, filter); + } + public static ModuleManager GetModule(this DiscordClient client) + where T : class, IModule + => client.GetService().Get(); + } +} diff --git a/discord.net/src/Discord.Net.Modules/ModuleFilter.cs b/discord.net/src/Discord.Net.Modules/ModuleFilter.cs new file mode 100644 index 000000000..08fa09a5d --- /dev/null +++ b/discord.net/src/Discord.Net.Modules/ModuleFilter.cs @@ -0,0 +1,17 @@ +using System; + +namespace Discord.Modules +{ + [Flags] + public enum ModuleFilter + { + /// Disables the event and command filters. + None = 0x0, + /// Uses the server whitelist to filter events and commands. + ServerWhitelist = 0x1, + /// Uses the channel whitelist to filter events and commands. + ChannelWhitelist = 0x2, + /// Enables this module in all private messages. + AlwaysAllowPrivate = 0x4 + } +} diff --git a/discord.net/src/Discord.Net.Modules/ModuleManager.cs b/discord.net/src/Discord.Net.Modules/ModuleManager.cs new file mode 100644 index 000000000..b00dc244f --- /dev/null +++ b/discord.net/src/Discord.Net.Modules/ModuleManager.cs @@ -0,0 +1,316 @@ +using Discord.Commands; +using Nito.AsyncEx; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Discord.Modules +{ + public class ModuleManager : ModuleManager + where T : class, IModule + { + public new T Instance => base.Instance as T; + + internal ModuleManager(DiscordClient client, T instance, string name, ModuleFilter filterType) + : base(client, instance, name, filterType) + { + } + } + + public class ModuleManager + { + public event EventHandler ServerEnabled = delegate { }; + public event EventHandler ServerDisabled = delegate { }; + public event EventHandler ChannelEnabled = delegate { }; + public event EventHandler ChannelDisabled = delegate { }; + + public event EventHandler JoinedServer = delegate { }; + public event EventHandler LeftServer = delegate { }; + public event EventHandler ServerUpdated = delegate { }; + public event EventHandler ServerUnavailable = delegate { }; + public event EventHandler ServerAvailable = delegate { }; + + public event EventHandler ChannelCreated = delegate { }; + public event EventHandler ChannelDestroyed = delegate { }; + public event EventHandler ChannelUpdated = delegate { }; + + public event EventHandler RoleCreated = delegate { }; + public event EventHandler RoleUpdated = delegate { }; + public event EventHandler RoleDeleted = delegate { }; + + public event EventHandler UserBanned = delegate { }; + public event EventHandler UserJoined = delegate { }; + public event EventHandler UserLeft = delegate { }; + public event EventHandler UserUpdated = delegate { }; + //public event EventHandler UserPresenceUpdated = delegate { }; + //public event EventHandler UserVoiceStateUpdated = delegate { }; + public event EventHandler UserUnbanned = delegate { }; + public event EventHandler UserIsTyping = delegate { }; + + public event EventHandler MessageReceived = delegate { }; + public event EventHandler MessageSent = delegate { }; + public event EventHandler MessageDeleted = delegate { }; + public event EventHandler MessageUpdated = delegate { }; + public event EventHandler MessageReadRemotely = delegate { }; + + private readonly bool _useServerWhitelist, _useChannelWhitelist, _allowAll, _allowPrivate; + private readonly ConcurrentDictionary _enabledServers; + private readonly ConcurrentDictionary _enabledChannels; + private readonly ConcurrentDictionary _indirectServers; + private readonly AsyncLock _lock; + + public DiscordClient Client { get; } + public IModule Instance { get; } + public string Name { get; } + public string Id { get; } + public ModuleFilter FilterType { get; } + + public IEnumerable EnabledServers => _enabledServers.Select(x => x.Value); + public IEnumerable EnabledChannels => _enabledChannels.Select(x => x.Value); + + internal ModuleManager(DiscordClient client, IModule instance, string name, ModuleFilter filterType) + { + Client = client; + Instance = instance; + Name = name; + FilterType = filterType; + + Id = name.ToLowerInvariant(); + _lock = new AsyncLock(); + + _allowAll = filterType == ModuleFilter.None; + _useServerWhitelist = filterType.HasFlag(ModuleFilter.ServerWhitelist); + _useChannelWhitelist = filterType.HasFlag(ModuleFilter.ChannelWhitelist); + _allowPrivate = filterType.HasFlag(ModuleFilter.AlwaysAllowPrivate); + + _enabledServers = new ConcurrentDictionary(); + _enabledChannels = new ConcurrentDictionary(); + _indirectServers = new ConcurrentDictionary(); + + if (_allowAll || _useServerWhitelist) //Server-only events + { + client.ChannelCreated += (s, e) => { if (e.Server != null && HasServer(e.Server)) ChannelCreated(s, e); }; + //TODO: This *is* a channel update if the before/after voice channel is whitelisted + //client.UserVoiceStateUpdated += (s, e) => { if (HasServer(e.Server)) UserVoiceStateUpdated(s, e); }; + } + + client.ChannelDestroyed += (s, e) => { if (HasChannel(e.Channel)) ChannelDestroyed(s, e); }; + client.ChannelUpdated += (s, e) => { if (HasChannel(e.After)) ChannelUpdated(s, e); }; + + client.MessageReceived += (s, e) => { if (HasChannel(e.Channel)) MessageReceived(s, e); }; + client.MessageSent += (s, e) => { if (HasChannel(e.Channel)) MessageSent(s, e); }; + client.MessageDeleted += (s, e) => { if (HasChannel(e.Channel)) MessageDeleted(s, e); }; + client.MessageUpdated += (s, e) => { if (HasChannel(e.Channel)) MessageUpdated(s, e); }; + client.MessageAcknowledged += (s, e) => { if (HasChannel(e.Channel)) MessageReadRemotely(s, e); }; + + client.RoleCreated += (s, e) => { if (HasIndirectServer(e.Server)) RoleCreated(s, e); }; + client.RoleUpdated += (s, e) => { if (HasIndirectServer(e.Server)) RoleUpdated(s, e); }; + client.RoleDeleted += (s, e) => { if (HasIndirectServer(e.Server)) RoleDeleted(s, e); }; + + client.JoinedServer += (s, e) => { if (_allowAll) JoinedServer(s, e); }; + client.LeftServer += (s, e) => { if (HasIndirectServer(e.Server)) LeftServer(s, e); }; + client.ServerUpdated += (s, e) => { if (HasIndirectServer(e.After)) ServerUpdated(s, e); }; + client.ServerUnavailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerUnavailable(s, e); }; + client.ServerAvailable += (s, e) => { if (HasIndirectServer(e.Server)) ServerAvailable(s, e); }; + + client.UserJoined += (s, e) => { if (HasIndirectServer(e.Server)) UserJoined(s, e); }; + client.UserLeft += (s, e) => { if (HasIndirectServer(e.Server)) UserLeft(s, e); }; + client.UserUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserUpdated(s, e); }; + client.UserIsTyping += (s, e) => { if (HasChannel(e.Channel)) UserIsTyping(s, e); }; + //TODO: We aren't getting events from UserPresence if AllowPrivate is enabled, but the server we know that user through isn't on the whitelist + //client.UserPresenceUpdated += (s, e) => { if (HasIndirectServer(e.Server)) UserPresenceUpdated(s, e); }; + client.UserBanned += (s, e) => { if (HasIndirectServer(e.Server)) UserBanned(s, e); }; + client.UserUnbanned += (s, e) => { if (HasIndirectServer(e.Server)) UserUnbanned(s, e); }; + } + + public void CreateCommands(string prefix, Action config) + { + var commandService = Client.GetService(); + commandService.CreateGroup(prefix, x => + { + x.Category(Name); + x.AddCheck(new ModuleChecker(this)); + config(x); + }); + + } + public bool EnableServer(Server server) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); + + using (_lock.Lock()) + return EnableServerInternal(server); + } + public void EnableServers(IEnumerable servers) + { + if (servers == null) throw new ArgumentNullException(nameof(servers)); + if (servers.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(servers)); + if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); + + using (_lock.Lock()) + { + foreach (var server in servers) + EnableServerInternal(server); + } + } + private bool EnableServerInternal(Server server) + { + if (_enabledServers.TryAdd(server.Id, server)) + { + if (ServerEnabled != null) + ServerEnabled(this, new ServerEventArgs(server)); + return true; + } + return false; + } + + public bool DisableServer(Server server) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + if (!_useServerWhitelist) return false; + + using (_lock.Lock()) + { + if (_enabledServers.TryRemove(server.Id, out server)) + { + if (ServerDisabled != null) + ServerDisabled(this, new ServerEventArgs(server)); + return true; + } + return false; + } + } + public void DisableAllServers() + { + if (!_useServerWhitelist) throw new InvalidOperationException("This module is not configured to use a server whitelist."); + if (!_useServerWhitelist) return; + + using (_lock.Lock()) + { + if (ServerDisabled != null) + { + foreach (var server in _enabledServers) + ServerDisabled(this, new ServerEventArgs(server.Value)); + } + + _enabledServers.Clear(); + } + } + + public bool EnableChannel(Channel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); + + using (_lock.Lock()) + return EnableChannelInternal(channel); + } + public void EnableChannels(IEnumerable channels) + { + if (channels == null) throw new ArgumentNullException(nameof(channels)); + if (channels.Contains(null)) throw new ArgumentException("Collection cannot contain null.", nameof(channels)); + if (!_useChannelWhitelist) throw new InvalidOperationException("This module is not configured to use a channel whitelist."); + + using (_lock.Lock()) + { + foreach (var channel in channels) + EnableChannelInternal(channel); + } + } + private bool EnableChannelInternal(Channel channel) + { + if (_enabledChannels.TryAdd(channel.Id, channel)) + { + var server = channel.Server; + if (server != null) + { + int value = 0; + _indirectServers.TryGetValue(server.Id, out value); + value++; + _indirectServers[server.Id] = value; + } + if (ChannelEnabled != null) + ChannelEnabled(this, new ChannelEventArgs(channel)); + return true; + } + return false; + } + + public bool DisableChannel(Channel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (!_useChannelWhitelist) return false; + + using (_lock.Lock()) + { + Channel ignored; + if (_enabledChannels.TryRemove(channel.Id, out ignored)) + { + var server = channel.Server; + if (server != null) + { + int value = 0; + _indirectServers.TryGetValue(server.Id, out value); + value--; + if (value <= 0) + _indirectServers.TryRemove(server.Id, out value); + else + _indirectServers[server.Id] = value; + } + if (ChannelDisabled != null) + ChannelDisabled(this, new ChannelEventArgs(channel)); + return true; + } + return false; + } + } + public void DisableAllChannels() + { + if (!_useChannelWhitelist) return; + + using (_lock.Lock()) + { + if (ChannelDisabled != null) + { + foreach (var channel in _enabledChannels) + ChannelDisabled(this, new ChannelEventArgs(channel.Value)); + } + + _enabledChannels.Clear(); + _indirectServers.Clear(); + } + } + + public void DisableAll() + { + if (_useServerWhitelist) + DisableAllServers(); + if (_useChannelWhitelist) + DisableAllChannels(); + } + + internal bool HasServer(Server server) => + _allowAll || + _useServerWhitelist && _enabledServers.ContainsKey(server.Id); + internal bool HasIndirectServer(Server server) => + _allowAll || + (_useServerWhitelist && _enabledServers.ContainsKey(server.Id)) || + (_useChannelWhitelist && _indirectServers.ContainsKey(server.Id)); + internal bool HasChannel(Channel channel) + { + if (_allowAll) return true; + if (channel.IsPrivate) return _allowPrivate; + + if (_useChannelWhitelist && _enabledChannels.ContainsKey(channel.Id)) return true; + if (_useServerWhitelist) + { + var server = channel.Server; + if (server == null) return false; + if (_enabledServers.ContainsKey(server.Id)) return true; + } + return false; + } + } +} diff --git a/discord.net/src/Discord.Net.Modules/ModuleService.cs b/discord.net/src/Discord.Net.Modules/ModuleService.cs new file mode 100644 index 000000000..1f405a222 --- /dev/null +++ b/discord.net/src/Discord.Net.Modules/ModuleService.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Discord.Modules +{ + public class ModuleService : IService + { + public DiscordClient Client { get; private set; } + + private static readonly MethodInfo addMethod = typeof(ModuleService).GetTypeInfo().GetDeclaredMethods(nameof(Add)) + .Single(x => x.IsGenericMethodDefinition && x.GetParameters().Length == 3); + + public IEnumerable Modules => _modules.Values; + private readonly Dictionary _modules; + + public ModuleService() + { + _modules = new Dictionary(); + } + + void IService.Install(DiscordClient client) + { + Client = client; + } + + public void Add(IModule instance, string name, ModuleFilter filter) + { + Type type = instance.GetType(); + addMethod.MakeGenericMethod(type).Invoke(this, new object[] { instance, name, filter }); + } + public void Add(string name, ModuleFilter filter) + where T : class, IModule, new() + => Add(new T(), name, filter); + public void Add(T instance, string name, ModuleFilter filter) + where T : class, IModule + { + if (instance == null) throw new ArgumentNullException(nameof(instance)); + if (Client == null) + throw new InvalidOperationException("Service needs to be added to a DiscordClient before modules can be installed."); + + Type type = typeof(T); + if (name == null) name = type.Name; + if (_modules.ContainsKey(type)) + throw new InvalidOperationException("This module has already been added."); + + var manager = new ModuleManager(Client, instance, name, filter); + _modules.Add(type, manager); + instance.Install(manager); + } + public ModuleManager Get() + where T : class, IModule + { + ModuleManager manager; + if (_modules.TryGetValue(typeof(T), out manager)) + return manager as ModuleManager; + return null; + } + } +} diff --git a/discord.net/src/Discord.Net.Modules/project.json b/discord.net/src/Discord.Net.Modules/project.json new file mode 100644 index 000000000..a2d23506e --- /dev/null +++ b/discord.net/src/Discord.Net.Modules/project.json @@ -0,0 +1,39 @@ +{ + "version": "0.9.2", + "description": "A Discord.Net extension adding basic plugin support.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ "discord", "discordapp" ], + "projectUrl": "https://github.com/RogueException/Discord.Net", + "licenseUrl": "http://opensource.org/licenses/MIT", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "buildOptions": { + "compile": [ "**/*.cs", "../Discord.Net.Shared/*.cs" ], + "warningsAsErrors": true + }, + + "dependencies": { + "Discord.Net": "0.9.2", + "Discord.Net.Commands": "0.9.2" + }, + + "frameworks": { + "netstandard1.3": { + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027" + }, + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + }, + "net45": {} + } +} diff --git a/discord.net/src/Discord.Net.Net45/Discord.Net.csproj b/discord.net/src/Discord.Net.Net45/Discord.Net.csproj new file mode 100644 index 000000000..6281ff4e4 --- /dev/null +++ b/discord.net/src/Discord.Net.Net45/Discord.Net.csproj @@ -0,0 +1,631 @@ + + + + + Debug + AnyCPU + {8D71A857-879A-4A10-859E-5FF824ED6688} + Library + Properties + Discord + Discord.Net + 512 + v4.5 + + + False + + + + true + full + false + bin\Debug\ + TRACE;DEBUG;NET45 + prompt + 2 + true + 6 + true + + + pdbonly + true + bin\Release\ + TRACE;NET45 + prompt + 4 + true + true + 6 + + + true + bin\FullDebug\ + TRACE;DEBUG;NET45,TEST_RESPONSES + true + 2 + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + false + 6 + true + + + + + + + + API\Client\Common\Channel.cs + + + API\Client\Common\ChannelReference.cs + + + API\Client\Common\ExtendedGuild.cs + + + API\Client\Common\ExtendedMember.cs + + + API\Client\Common\Game.cs + + + API\Client\Common\Guild.cs + + + API\Client\Common\GuildReference.cs + + + API\Client\Common\Invite.cs + + + API\Client\Common\InviteReference.cs + + + API\Client\Common\Member.cs + + + API\Client\Common\MemberPresence.cs + + + API\Client\Common\MemberReference.cs + + + API\Client\Common\MemberVoiceState.cs + + + API\Client\Common\Message.cs + + + API\Client\Common\MessageReference.cs + + + API\Client\Common\Role.cs + + + API\Client\Common\RoleReference.cs + + + API\Client\Common\User.cs + + + API\Client\Common\UserReference.cs + + + API\Client\GatewaySocket\Commands\Heartbeat.cs + + + API\Client\GatewaySocket\Commands\Identify.cs + + + API\Client\GatewaySocket\Commands\RequestMembers.cs + + + API\Client\GatewaySocket\Commands\Resume.cs + + + API\Client\GatewaySocket\Commands\UpdateStatus.cs + + + API\Client\GatewaySocket\Commands\UpdateVoice.cs + + + API\Client\GatewaySocket\Events\ChannelCreate.cs + + + API\Client\GatewaySocket\Events\ChannelDelete.cs + + + API\Client\GatewaySocket\Events\ChannelUpdate.cs + + + API\Client\GatewaySocket\Events\GuildBanAdd.cs + + + API\Client\GatewaySocket\Events\GuildBanRemove.cs + + + API\Client\GatewaySocket\Events\GuildCreate.cs + + + API\Client\GatewaySocket\Events\GuildDelete.cs + + + API\Client\GatewaySocket\Events\GuildEmojisUpdate.cs + + + API\Client\GatewaySocket\Events\GuildIntegrationsUpdate.cs + + + API\Client\GatewaySocket\Events\GuildMemberAdd.cs + + + API\Client\GatewaySocket\Events\GuildMemberRemove.cs + + + API\Client\GatewaySocket\Events\GuildMembersChunk.cs + + + API\Client\GatewaySocket\Events\GuildMemberUpdate.cs + + + API\Client\GatewaySocket\Events\GuildRoleCreate.cs + + + API\Client\GatewaySocket\Events\GuildRoleDelete.cs + + + API\Client\GatewaySocket\Events\GuildRoleUpdate.cs + + + API\Client\GatewaySocket\Events\GuildUpdate.cs + + + API\Client\GatewaySocket\Events\MessageAck.cs + + + API\Client\GatewaySocket\Events\MessageCreate.cs + + + API\Client\GatewaySocket\Events\MessageDelete.cs + + + API\Client\GatewaySocket\Events\MessageUpdate.cs + + + API\Client\GatewaySocket\Events\PresenceUpdate.cs + + + API\Client\GatewaySocket\Events\Ready.cs + + + API\Client\GatewaySocket\Events\Reconnect.cs + + + API\Client\GatewaySocket\Events\Resumed.cs + + + API\Client\GatewaySocket\Events\TypingStart.cs + + + API\Client\GatewaySocket\Events\UserSettingsUpdate.cs + + + API\Client\GatewaySocket\Events\UserUpdate.cs + + + API\Client\GatewaySocket\Events\VoiceServerUpdate.cs + + + API\Client\GatewaySocket\Events\VoiceStateUpdate.cs + + + API\Client\GatewaySocket\OpCodes.cs + + + API\Client\IWebSocketMessage.cs + + + API\Client\Rest\AcceptInvite.cs + + + API\Client\Rest\AckMessage.cs + + + API\Client\Rest\AddChannelPermission.cs + + + API\Client\Rest\AddGuildBan.cs + + + API\Client\Rest\CreateChannel.cs + + + API\Client\Rest\CreateGuild.cs + + + API\Client\Rest\CreateInvite.cs + + + API\Client\Rest\CreatePrivateChannel.cs + + + API\Client\Rest\CreateRole.cs + + + API\Client\Rest\DeleteChannel.cs + + + API\Client\Rest\DeleteGuild.cs + + + API\Client\Rest\DeleteInvite.cs + + + API\Client\Rest\DeleteMessage.cs + + + API\Client\Rest\DeleteRole.cs + + + API\Client\Rest\Gateway.cs + + + API\Client\Rest\GetBans.cs + + + API\Client\Rest\GetInvite.cs + + + API\Client\Rest\GetInvites.cs + + + API\Client\Rest\GetMessages.cs + + + API\Client\Rest\GetVoiceRegions.cs + + + API\Client\Rest\GetWidget.cs + + + API\Client\Rest\KickMember.cs + + + API\Client\Rest\LeaveGuild.cs + + + API\Client\Rest\Login.cs + + + API\Client\Rest\Logout.cs + + + API\Client\Rest\PruneMembers.cs + + + API\Client\Rest\RemoveChannelPermission.cs + + + API\Client\Rest\RemoveGuildBan.cs + + + API\Client\Rest\ReorderChannels.cs + + + API\Client\Rest\ReorderRoles.cs + + + API\Client\Rest\SendFile.cs + + + API\Client\Rest\SendIsTyping.cs + + + API\Client\Rest\SendMessage.cs + + + API\Client\Rest\UpdateChannel.cs + + + API\Client\Rest\UpdateGuild.cs + + + API\Client\Rest\UpdateMember.cs + + + API\Client\Rest\UpdateMessage.cs + + + API\Client\Rest\UpdateOwnNick.cs + + + API\Client\Rest\UpdateProfile.cs + + + API\Client\Rest\UpdateRole.cs + + + API\Client\VoiceSocket\Commands\Heartbeat.cs + + + API\Client\VoiceSocket\Commands\Identify.cs + + + API\Client\VoiceSocket\Commands\SelectProtocol.cs + + + API\Client\VoiceSocket\Commands\SetSpeaking.cs + + + API\Client\VoiceSocket\Events\Ready.cs + + + API\Client\VoiceSocket\Events\SessionDescription.cs + + + API\Client\VoiceSocket\Events\Speaking.cs + + + API\Client\VoiceSocket\OpCodes.cs + + + API\Converters.cs + + + API\Extensions.cs + + + API\IRestRequest.cs + + + API\Status\Common\StatusResult.cs + + + API\Status\Rest\ActiveMaintenances.cs + + + API\Status\Rest\AllIncidents.cs + + + API\Status\Rest\UnresolvedIncidents.cs + + + API\Status\Rest\UpcomingMaintenances.cs + + + DiscordClient.cs + + + DiscordClient.Events.cs + + + DiscordConfig.cs + + + DynamicIL.cs + + + Enums\ChannelType.cs + + + Enums\ConnectionState.cs + + + Enums\ImageType.cs + + + Enums\LogSeverity.cs + + + Enums\PermissionBits.cs + + + Enums\PermissionTarget.cs + + + Enums\PermValue.cs + + + Enums\Relative.cs + + + Enums\StringEnum.cs + + + Enums\UserStatus.cs + + + ETF\ETFReader.cs + + + ETF\ETFType.cs + + + ETF\ETFWriter.cs + + + Events\ChannelEventArgs.cs + + + Events\ChannelUpdatedEventArgs.cs + + + Events\ChannelUserEventArgs.cs + + + Events\DisconnectedEventArgs.cs + + + Events\LogMessageEventArgs.cs + + + Events\MessageEventArgs.cs + + + Events\MessageUpdatedEventArgs.cs + + + Events\ProfileUpdatedEventArgs.cs + + + Events\RoleEventArgs.cs + + + Events\RoleUpdatedEventArgs.cs + + + Events\ServerEventArgs.cs + + + Events\ServerUpdatedEventArgs.cs + + + Events\UserEventArgs.cs + + + Events\UserUpdatedEventArgs.cs + + + Format.cs + + + IMentionable.cs + + + InternalExtensions.cs + + + IService.cs + + + Legacy.cs + + + Logging\ILogger.cs + + + Logging\Logger.cs + + + Logging\LogManager.cs + + + MessageQueue.cs + + + Models\Channel.cs + + + Models\Color.cs + + + Models\Game.cs + + + Models\Invite.cs + + + Models\Message.cs + + + Models\Permissions.cs + + + Models\Profile.cs + + + Models\Region.cs + + + Models\Role.cs + + + Models\Server.cs + + + Models\User.cs + + + Net\HttpException.cs + + + Net\Rest\CompletedRequestEventArgs.cs + + + Net\Rest\IRestEngine.cs + + + Net\Rest\JsonRestClient.cs + + + Net\Rest\RequestEventArgs.cs + + + Net\Rest\RestClient.cs + + + Net\Rest\SharpRestEngine.cs + + + Net\TimeoutException.cs + + + Net\WebSockets\WebSocketException.cs + + + Net\WebSockets\BinaryMessageEventArgs.cs + + + Net\WebSockets\BuiltInEngine.cs + + + Net\WebSockets\GatewaySocket.cs + + + Net\WebSockets\IWebSocketEngine.cs + + + Net\WebSockets\TextMessageEventArgs.cs + + + Net\WebSockets\WebSocket.cs + + + Net\WebSockets\WebSocketEventEventArgs.cs + + + Net\WebSockets\WS4NetEngine.cs + + + ServiceCollection.cs + + + TaskManager.cs + + + + + + + project.json + + + + + + + + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Net45/Enums/GameType.cs b/discord.net/src/Discord.Net.Net45/Enums/GameType.cs new file mode 100644 index 000000000..446271b47 --- /dev/null +++ b/discord.net/src/Discord.Net.Net45/Enums/GameType.cs @@ -0,0 +1,8 @@ +namespace Discord +{ + public enum GameType : int + { + Default = 0, // "NotStreaming", pretty much + Twitch + } +} diff --git a/discord.net/src/Discord.Net.Net45/Properties/AssemblyInfo.cs b/discord.net/src/Discord.Net.Net45/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..03ce14661 --- /dev/null +++ b/discord.net/src/Discord.Net.Net45/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Discord.Net")] +[assembly: AssemblyDescription("An unofficial .Net API wrapper for the Discord client.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("RogueException")] +[assembly: AssemblyProduct("Discord.Net")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] +[assembly: Guid("76ea00e6-ea24-41e1-acb2-639c0313fa80")] + +[assembly: AssemblyVersion("0.9.2.0")] +[assembly: AssemblyFileVersion("0.9.2.0")] diff --git a/discord.net/src/Discord.Net.Net45/project.json b/discord.net/src/Discord.Net.Net45/project.json new file mode 100644 index 000000000..636d3ac09 --- /dev/null +++ b/discord.net/src/Discord.Net.Net45/project.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "Newtonsoft.Json": "8.0.4-beta1", + "Nito.AsyncEx": "3.0.1", + "RestSharp": "105.2.3", + "WebSocket4Net": "0.14.1" + }, + "frameworks": { + "net45": {} + }, + "runtimes": { + "win": {}, + "win-x86": {}, + "win-x64": {} + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Shared/Discord.Net.Shared.projitems b/discord.net/src/Discord.Net.Shared/Discord.Net.Shared.projitems new file mode 100644 index 000000000..38e4eafc8 --- /dev/null +++ b/discord.net/src/Discord.Net.Shared/Discord.Net.Shared.projitems @@ -0,0 +1,16 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 2875deb5-f248-4105-8ea2-5141e3de8025 + + + Discord.Net.Shared + + + + + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net.Shared/Discord.Net.Shared.shproj b/discord.net/src/Discord.Net.Shared/Discord.Net.Shared.shproj new file mode 100644 index 000000000..5d4d53951 --- /dev/null +++ b/discord.net/src/Discord.Net.Shared/Discord.Net.Shared.shproj @@ -0,0 +1,13 @@ + + + + 2875deb5-f248-4105-8ea2-5141e3de8025 + 14.0 + + + + + + + + diff --git a/discord.net/src/Discord.Net.Shared/EpochTime.cs b/discord.net/src/Discord.Net.Shared/EpochTime.cs new file mode 100644 index 000000000..b4dd03fe9 --- /dev/null +++ b/discord.net/src/Discord.Net.Shared/EpochTime.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord +{ + internal class EpochTime + { + private const long epoch = 621355968000000000L; //1/1/1970 in Ticks + + public static long GetMilliseconds() => (DateTime.UtcNow.Ticks - epoch) / TimeSpan.TicksPerMillisecond; + } +} diff --git a/discord.net/src/Discord.Net.Shared/TaskExtensions.cs b/discord.net/src/Discord.Net.Shared/TaskExtensions.cs new file mode 100644 index 000000000..a6828c7fb --- /dev/null +++ b/discord.net/src/Discord.Net.Shared/TaskExtensions.cs @@ -0,0 +1,68 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord +{ + internal static class TaskExtensions + { + public static async Task Timeout(this Task task, int milliseconds) + { + Task timeoutTask = Task.Delay(milliseconds); + Task finishedTask = await Task.WhenAny(task, timeoutTask).ConfigureAwait(false); + if (finishedTask == timeoutTask) + throw new TimeoutException(); + else + await task.ConfigureAwait(false); + } + public static async Task Timeout(this Task task, int milliseconds) + { + Task timeoutTask = Task.Delay(milliseconds); + Task finishedTask = await Task.WhenAny(task, timeoutTask).ConfigureAwait(false); + if (finishedTask == timeoutTask) + throw new TimeoutException(); + else + return await task.ConfigureAwait(false); + } + public static async Task Timeout(this Task task, int milliseconds, CancellationTokenSource timeoutToken) + { + try + { + timeoutToken.CancelAfter(milliseconds); + await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + if (timeoutToken.IsCancellationRequested) + throw new TimeoutException(); + throw; + } + } + public static async Task Timeout(this Task task, int milliseconds, CancellationTokenSource timeoutToken) + { + try + { + timeoutToken.CancelAfter(milliseconds); + return await task.ConfigureAwait(false); + } + catch (OperationCanceledException) + { + if (timeoutToken.IsCancellationRequested) + throw new TimeoutException(); + throw; + } + } + + public static async Task Wait(this CancellationTokenSource cancelTokenSource) + { + var cancelToken = cancelTokenSource.Token; + try { await Task.Delay(-1, cancelToken).ConfigureAwait(false); } + catch (OperationCanceledException) { } //Expected + } + public static async Task Wait(this CancellationToken cancelToken) + { + try { await Task.Delay(-1, cancelToken).ConfigureAwait(false); } + catch (OperationCanceledException) { } //Expected + } + } +} diff --git a/discord.net/src/Discord.Net.Shared/TaskHelper.cs b/discord.net/src/Discord.Net.Shared/TaskHelper.cs new file mode 100644 index 000000000..83408b8e9 --- /dev/null +++ b/discord.net/src/Discord.Net.Shared/TaskHelper.cs @@ -0,0 +1,29 @@ +using System; +using System.Threading.Tasks; + +namespace Discord +{ + internal static class TaskHelper + { +#if DOTNET54 + public static Task CompletedTask => Task.CompletedTask; +#else + public static Task CompletedTask => Task.Delay(0); +#endif + + public static Func ToAsync(Action action) + { + return () => + { + action(); return CompletedTask; + }; + } + public static Func ToAsync(Action action) + { + return x => + { + action(x); return CompletedTask; + }; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/Channel.cs b/discord.net/src/Discord.Net/API/Client/Common/Channel.cs new file mode 100644 index 000000000..90ed8bf38 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/Channel.cs @@ -0,0 +1,33 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class Channel : ChannelReference + { + public class PermissionOverwrite + { + [JsonProperty("type")] + public string Type { get; set; } + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("deny")] + public uint Deny { get; set; } + [JsonProperty("allow")] + public uint Allow { get; set; } + } + + [JsonProperty("last_message_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? LastMessageId { get; set; } + [JsonProperty("is_private")] + public bool? IsPrivate { get; set; } + [JsonProperty("position")] + public int? Position { get; set; } + [JsonProperty("topic")] + public string Topic { get; set; } + [JsonProperty("permission_overwrites")] + public PermissionOverwrite[] PermissionOverwrites { get; set; } + [JsonProperty("recipient")] + public UserReference Recipient { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/ChannelReference.cs b/discord.net/src/Discord.Net/API/Client/Common/ChannelReference.cs new file mode 100644 index 000000000..a243e0f49 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/ChannelReference.cs @@ -0,0 +1,17 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class ChannelReference + { + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("guild_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? GuildId { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("type")] + public string Type { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/ExtendedGuild.cs b/discord.net/src/Discord.Net/API/Client/Common/ExtendedGuild.cs new file mode 100644 index 000000000..63c55eddb --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/ExtendedGuild.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class ExtendedGuild : Guild + { + [JsonProperty("member_count")] + public int? MemberCount { get; set; } + [JsonProperty("large")] + public bool IsLarge { get; set; } + [JsonProperty("unavailable")] + public bool? Unavailable { get; set; } + + [JsonProperty("channels")] + public Channel[] Channels { get; set; } + [JsonProperty("members")] + public ExtendedMember[] Members { get; set; } + [JsonProperty("presences")] + public MemberPresence[] Presences { get; set; } + [JsonProperty("voice_states")] + public MemberVoiceState[] VoiceStates { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/ExtendedMember.cs b/discord.net/src/Discord.Net/API/Client/Common/ExtendedMember.cs new file mode 100644 index 000000000..890ec9de5 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/ExtendedMember.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class ExtendedMember : Member + { + [JsonProperty("mute")] + public bool? IsServerMuted { get; set; } + [JsonProperty("deaf")] + public bool? IsServerDeafened { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/Game.cs b/discord.net/src/Discord.Net/API/Client/Common/Game.cs new file mode 100644 index 000000000..e41306cdc --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/Game.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class Game + { + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("type")] + public GameType Type { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/Guild.cs b/discord.net/src/Discord.Net/API/Client/Common/Guild.cs new file mode 100644 index 000000000..1ee4a0b51 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/Guild.cs @@ -0,0 +1,50 @@ +using Discord.API.Converters; +using Newtonsoft.Json; +using System; + +namespace Discord.API.Client +{ + public class Guild : GuildReference + { + public class EmojiData + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("roles"), JsonConverter(typeof(LongStringArrayConverter))] + public ulong[] RoleIds { get; set; } + [JsonProperty("require_colons")] + public bool RequireColons { get; set; } + [JsonProperty("managed")] + public bool IsManaged { get; set; } + } + + [JsonProperty("afk_channel_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? AFKChannelId { get; set; } + [JsonProperty("afk_timeout")] + public int? AFKTimeout { get; set; } + [JsonProperty("embed_channel_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? EmbedChannelId { get; set; } + [JsonProperty("embed_enabled")] + public bool EmbedEnabled { get; set; } + [JsonProperty("icon")] + public string Icon { get; set; } + [JsonProperty("joined_at")] + public DateTime? JoinedAt { get; set; } + [JsonProperty("owner_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? OwnerId { get; set; } + [JsonProperty("region")] + public string Region { get; set; } + [JsonProperty("roles")] + public Role[] Roles { get; set; } + [JsonProperty("features")] + public string[] Features { get; set; } + [JsonProperty("emojis")] + public EmojiData[] Emojis { get; set; } + [JsonProperty("splash")] + public string Splash { get; set; } + [JsonProperty("verification_level")] + public int VerificationLevel { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/GuildReference.cs b/discord.net/src/Discord.Net/API/Client/Common/GuildReference.cs new file mode 100644 index 000000000..5b87c3cc2 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/GuildReference.cs @@ -0,0 +1,13 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class GuildReference + { + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/Invite.cs b/discord.net/src/Discord.Net/API/Client/Common/Invite.cs new file mode 100644 index 000000000..571f551ee --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/Invite.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; +using System; + +namespace Discord.API.Client +{ + public class Invite : InviteReference + { + [JsonProperty("max_age")] + public int? MaxAge { get; set; } + [JsonProperty("max_uses")] + public int? MaxUses { get; set; } + [JsonProperty("revoked")] + public bool? IsRevoked { get; set; } + [JsonProperty("temporary")] + public bool? IsTemporary { get; set; } + [JsonProperty("uses")] + public int? Uses { get; set; } + [JsonProperty("created_at")] + public DateTime? CreatedAt { get; set; } + [JsonProperty("inviter")] + public UserReference Inviter { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/InviteReference.cs b/discord.net/src/Discord.Net/API/Client/Common/InviteReference.cs new file mode 100644 index 000000000..194165173 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/InviteReference.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class InviteReference + { + public class GuildData : GuildReference + { + [JsonProperty("splash_hash")] + public string Splash { get; set; } + } + + [JsonProperty("guild")] + public GuildData Guild { get; set; } + [JsonProperty("channel")] + public ChannelReference Channel { get; set; } + [JsonProperty("code")] + public string Code { get; set; } + [JsonProperty("xkcdpass")] + public string XkcdPass { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/Member.cs b/discord.net/src/Discord.Net/API/Client/Common/Member.cs new file mode 100644 index 000000000..297b75d8c --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/Member.cs @@ -0,0 +1,16 @@ +using Discord.API.Converters; +using Newtonsoft.Json; +using System; + +namespace Discord.API.Client +{ + public class Member : MemberReference + { + [JsonProperty("joined_at")] + public DateTime? JoinedAt { get; set; } + [JsonProperty("roles"), JsonConverter(typeof(LongStringArrayConverter))] + public ulong[] Roles { get; set; } + [JsonProperty("nick")] + public string Nick { get; set; } = ""; + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/MemberPresence.cs b/discord.net/src/Discord.Net/API/Client/Common/MemberPresence.cs new file mode 100644 index 000000000..331bbe730 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/MemberPresence.cs @@ -0,0 +1,15 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class MemberPresence : MemberReference + { + [JsonProperty("game")] + public Game Game { get; set; } + [JsonProperty("status")] + public string Status { get; set; } + [JsonProperty("roles"), JsonConverter(typeof(LongStringArrayConverter))] + public ulong[] Roles { get; set; } //TODO: Might be temporary + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/MemberReference.cs b/discord.net/src/Discord.Net/API/Client/Common/MemberReference.cs new file mode 100644 index 000000000..ba6f37762 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/MemberReference.cs @@ -0,0 +1,13 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class MemberReference + { + [JsonProperty("guild_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? GuildId { get; set; } + [JsonProperty("user")] + public UserReference User { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/MemberVoiceState.cs b/discord.net/src/Discord.Net/API/Client/Common/MemberVoiceState.cs new file mode 100644 index 000000000..4aab1774c --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/MemberVoiceState.cs @@ -0,0 +1,31 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class MemberVoiceState + { + [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] + public ulong GuildId { get; set; } + [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] + public ulong UserId { get; set; } + + [JsonProperty("channel_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? ChannelId { get; set; } + [JsonProperty("session_id")] + public string SessionId { get; set; } + [JsonProperty("token")] + public string Token { get; set; } + + [JsonProperty("self_mute")] + public bool? IsSelfMuted { get; set; } + [JsonProperty("self_deaf")] + public bool? IsSelfDeafened { get; set; } + [JsonProperty("mute")] + public bool? IsServerMuted { get; set; } + [JsonProperty("deaf")] + public bool? IsServerDeafened { get; set; } + [JsonProperty("suppress")] + public bool? IsServerSuppressed { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/Message.cs b/discord.net/src/Discord.Net/API/Client/Common/Message.cs new file mode 100644 index 000000000..7e9271dc9 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/Message.cs @@ -0,0 +1,96 @@ +using Newtonsoft.Json; +using System; + +namespace Discord.API.Client +{ + public class Message : MessageReference + { + public class Attachment + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("proxy_url")] + public string ProxyUrl { get; set; } + [JsonProperty("size")] + public int Size { get; set; } + [JsonProperty("filename")] + public string Filename { get; set; } + [JsonProperty("width")] + public int? Width { get; set; } + [JsonProperty("height")] + public int? Height { get; set; } + } + + public class Embed + { + public class Reference + { + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + } + + public class ThumbnailInfo + { + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("proxy_url")] + public string ProxyUrl { get; set; } + [JsonProperty("width")] + public int? Width { get; set; } + [JsonProperty("height")] + public int? Height { get; set; } + } + public class VideoInfo + { + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("width")] + public int? Width { get; set; } + [JsonProperty("height")] + public int? Height { get; set; } + } + + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("type")] + public string Type { get; set; } + [JsonProperty("title")] + public string Title { get; set; } + [JsonProperty("description")] + public string Description { get; set; } + [JsonProperty("author")] + public Reference Author { get; set; } + [JsonProperty("provider")] + public Reference Provider { get; set; } + [JsonProperty("thumbnail")] + public ThumbnailInfo Thumbnail { get; set; } + [JsonProperty("video")] + public VideoInfo Video { get; set; } + } + + [JsonProperty("tts")] + public bool? IsTextToSpeech { get; set; } + [JsonProperty("mention_everyone")] + public bool? IsMentioningEveryone { get; set; } + [JsonProperty("timestamp")] + public DateTime? Timestamp { get; set; } + [JsonProperty("edited_timestamp")] + public DateTime? EditedTimestamp { get; set; } + [JsonProperty("mentions")] + public UserReference[] Mentions { get; set; } + [JsonProperty("embeds")] + public Embed[] Embeds { get; set; } //TODO: Parse this + [JsonProperty("attachments")] + public Attachment[] Attachments { get; set; } + [JsonProperty("content")] + public string Content { get; set; } + [JsonProperty("author")] + public UserReference Author { get; set; } + [JsonProperty("nonce")] + public string Nonce { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/MessageReference.cs b/discord.net/src/Discord.Net/API/Client/Common/MessageReference.cs new file mode 100644 index 000000000..c2afa6cd5 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/MessageReference.cs @@ -0,0 +1,15 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class MessageReference + { + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("message_id"), JsonConverter(typeof(LongStringConverter))] //Only used in MESSAGE_ACK + public ulong MessageId { get { return Id; } set { Id = value; } } + [JsonProperty("channel_id"), JsonConverter(typeof(LongStringConverter))] + public ulong ChannelId { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/Role.cs b/discord.net/src/Discord.Net/API/Client/Common/Role.cs new file mode 100644 index 000000000..7d48748dd --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/Role.cs @@ -0,0 +1,25 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class Role + { + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("permissions")] + public uint? Permissions { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("position")] + public int? Position { get; set; } + [JsonProperty("hoist")] + public bool? Hoist { get; set; } + [JsonProperty("color")] + public uint? Color { get; set; } + [JsonProperty("managed")] + public bool? Managed { get; set; } + [JsonProperty("mentionable")] + public bool? Mentionable { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/RoleReference.cs b/discord.net/src/Discord.Net/API/Client/Common/RoleReference.cs new file mode 100644 index 000000000..ce7d25fcc --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/RoleReference.cs @@ -0,0 +1,13 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class RoleReference + { + [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] + public ulong GuildId { get; set; } + [JsonProperty("role_id"), JsonConverter(typeof(LongStringConverter))] + public ulong RoleId { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/User.cs b/discord.net/src/Discord.Net/API/Client/Common/User.cs new file mode 100644 index 000000000..86c6f22f6 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/User.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class User : UserReference + { + [JsonProperty("email")] + public string Email { get; set; } + [JsonProperty("verified")] + public bool? IsVerified { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Common/UserReference.cs b/discord.net/src/Discord.Net/API/Client/Common/UserReference.cs new file mode 100644 index 000000000..24dc91d52 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Common/UserReference.cs @@ -0,0 +1,19 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public class UserReference + { + [JsonProperty("username")] + public string Username { get; set; } + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("discriminator")] + public ushort? Discriminator { get; set; } + [JsonProperty("avatar")] + public string Avatar { get; set; } + [JsonProperty("bot")] + public bool? Bot { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs new file mode 100644 index 000000000..9f3f9cefb --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Heartbeat.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + [JsonObject(MemberSerialization.OptIn)] + public class HeartbeatCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; + object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); + bool IWebSocketMessage.IsPrivate => false; + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs new file mode 100644 index 000000000..f3141e56c --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Identify.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Discord.API.Client.GatewaySocket +{ + [JsonObject(MemberSerialization.OptIn)] + public class IdentifyCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.Identify; + object IWebSocketMessage.Payload => this; + bool IWebSocketMessage.IsPrivate => false; + + [JsonProperty("token")] + public string Token { get; set; } + [JsonProperty("properties")] + public Dictionary Properties { get; set; } + [JsonProperty("large_threshold", NullValueHandling = NullValueHandling.Ignore)] + public int LargeThreshold { get; set; } + [JsonProperty("compress")] + public bool UseCompression { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs new file mode 100644 index 000000000..cc3c93176 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/RequestMembers.cs @@ -0,0 +1,20 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + [JsonObject(MemberSerialization.OptIn)] + public class RequestMembersCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.RequestGuildMembers; + object IWebSocketMessage.Payload => this; + bool IWebSocketMessage.IsPrivate => false; + + [JsonProperty("guild_id"), JsonConverter(typeof(LongStringArrayConverter))] + public ulong[] GuildId { get; set; } + [JsonProperty("query")] + public string Query { get; set; } + [JsonProperty("limit")] + public int Limit { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs new file mode 100644 index 000000000..15486e577 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/Resume.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + [JsonObject(MemberSerialization.OptIn)] + public class ResumeCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.Resume; + object IWebSocketMessage.Payload => this; + bool IWebSocketMessage.IsPrivate => false; + + [JsonProperty("session_id")] + public string SessionId { get; set; } + [JsonProperty("seq")] + public uint Sequence { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs new file mode 100644 index 000000000..4ebe5633b --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateStatus.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateStatusCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.StatusUpdate; + object IWebSocketMessage.Payload => this; + bool IWebSocketMessage.IsPrivate => false; + + [JsonProperty("idle_since")] + public long? IdleSince { get; set; } + [JsonProperty("game")] + public Game Game { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs new file mode 100644 index 000000000..3ccf92c65 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Commands/UpdateVoice.cs @@ -0,0 +1,22 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateVoiceCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.VoiceStateUpdate; + object IWebSocketMessage.Payload => this; + bool IWebSocketMessage.IsPrivate => false; + + [JsonProperty("guild_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? GuildId { get; set; } + [JsonProperty("channel_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? ChannelId { get; set; } + [JsonProperty("self_mute")] + public bool IsSelfMuted { get; set; } + [JsonProperty("self_deaf")] + public bool IsSelfDeafened { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs new file mode 100644 index 000000000..ca26fecc7 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelCreate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class ChannelCreateEvent : Channel { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs new file mode 100644 index 000000000..2b61a7d78 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelDelete.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class ChannelDeleteEvent : Channel { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs new file mode 100644 index 000000000..4565ce1bc --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/ChannelUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class ChannelUpdateEvent : Channel { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs new file mode 100644 index 000000000..7ba24473a --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanAdd.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildBanAddEvent : MemberReference { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs new file mode 100644 index 000000000..a56a98494 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildBanRemove.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildBanRemoveEvent : MemberReference { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs new file mode 100644 index 000000000..41c1c71c7 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildCreate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildCreateEvent : ExtendedGuild { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs new file mode 100644 index 000000000..cf824c40e --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildDelete.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildDeleteEvent : ExtendedGuild { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs new file mode 100644 index 000000000..06255bdcf --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildEmojisUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket.Events +{ + //public class GuildEmojisUpdateEvent { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs new file mode 100644 index 000000000..0767b2f8f --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildIntegrationsUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + //public class GuildIntegrationsUpdateEvent { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs new file mode 100644 index 000000000..a2ce6ddb2 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberAdd.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildMemberAddEvent : ExtendedMember { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs new file mode 100644 index 000000000..311186b11 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberRemove.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildMemberRemoveEvent : Member { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs new file mode 100644 index 000000000..9b56a95b0 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMemberUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildMemberUpdateEvent : Member { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs new file mode 100644 index 000000000..4f2d36b8a --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildMembersChunk.cs @@ -0,0 +1,13 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + public class GuildMembersChunkEvent + { + [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] + public ulong GuildId { get; set; } + [JsonProperty("members")] + public ExtendedMember[] Members { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs new file mode 100644 index 000000000..3d8e2f459 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleCreate.cs @@ -0,0 +1,13 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + public class GuildRoleCreateEvent + { + [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] + public ulong GuildId { get; set; } + [JsonProperty("role")] + public Role Data { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs new file mode 100644 index 000000000..2ecd2edc5 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleDelete.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildRoleDeleteEvent : RoleReference { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs new file mode 100644 index 000000000..e26b65c4d --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildRoleUpdate.cs @@ -0,0 +1,13 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + public class GuildRoleUpdateEvent + { + [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] + public ulong GuildId { get; set; } + [JsonProperty("role")] + public Role Data { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs new file mode 100644 index 000000000..8fc0f1350 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/GuildUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class GuildUpdateEvent : Guild { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs new file mode 100644 index 000000000..64c106ef5 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageAck.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class MessageAckEvent : MessageReference { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs new file mode 100644 index 000000000..d6d2ec1cc --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageCreate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class MessageCreateEvent : Message { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs new file mode 100644 index 000000000..cfc2df7ff --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageDelete.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class MessageDeleteEvent : MessageReference { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs new file mode 100644 index 000000000..23521fd93 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/MessageUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class MessageUpdateEvent : Message { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs new file mode 100644 index 000000000..c40853336 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/PresenceUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class PresenceUpdateEvent : MemberPresence { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs new file mode 100644 index 000000000..744e5b4b5 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Ready.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + public class ReadyEvent + { + public class ReadState + { + [JsonProperty("id")] + public string ChannelId { get; set; } + [JsonProperty("mention_count")] + public int MentionCount { get; set; } + [JsonProperty("last_message_id")] + public string LastMessageId { get; set; } + } + + [JsonProperty("v")] + public int Version { get; set; } + [JsonProperty("user")] + public User User { get; set; } + [JsonProperty("session_id")] + public string SessionId { get; set; } + [JsonProperty("read_state")] + public ReadState[] ReadStates { get; set; } + [JsonProperty("guilds")] + public ExtendedGuild[] Guilds { get; set; } + [JsonProperty("private_channels")] + public Channel[] PrivateChannels { get; set; } + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval { get; set; } + + //Ignored + [JsonProperty("user_settings")] + public object UserSettings { get; set; } + [JsonProperty("user_guild_settings")] + public object UserGuildSettings { get; set; } + [JsonProperty("tutorial")] + public object Tutorial { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Reconnect.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Reconnect.cs new file mode 100644 index 000000000..7ceaa3046 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Reconnect.cs @@ -0,0 +1,6 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + public class ReconnectEvent { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs new file mode 100644 index 000000000..6a50fbe32 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/Resumed.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + public class ResumedEvent + { + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs new file mode 100644 index 000000000..484cec1bc --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/TypingStart.cs @@ -0,0 +1,15 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + public class TypingStartEvent + { + [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] + public ulong UserId { get; set; } + [JsonProperty("channel_id"), JsonConverter(typeof(LongStringConverter))] + public ulong ChannelId { get; set; } + [JsonProperty("timestamp")] + public int Timestamp { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs new file mode 100644 index 000000000..aad938157 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/UserSettingsUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + //public class UserSettingsUpdateEvent { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs new file mode 100644 index 000000000..3c366310a --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/UserUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class UserUpdateEvent : User { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs new file mode 100644 index 000000000..d305642a1 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceServerUpdate.cs @@ -0,0 +1,15 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.GatewaySocket +{ + public class VoiceServerUpdateEvent + { + [JsonProperty("guild_id"), JsonConverter(typeof(LongStringConverter))] + public ulong GuildId { get; set; } + [JsonProperty("endpoint")] + public string Endpoint { get; set; } + [JsonProperty("token")] + public string Token { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs new file mode 100644 index 000000000..f3ba96b17 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/Events/VoiceStateUpdate.cs @@ -0,0 +1,4 @@ +namespace Discord.API.Client.GatewaySocket +{ + public class VoiceStateUpdateEvent : MemberVoiceState { } +} diff --git a/discord.net/src/Discord.Net/API/Client/GatewaySocket/OpCodes.cs b/discord.net/src/Discord.Net/API/Client/GatewaySocket/OpCodes.cs new file mode 100644 index 000000000..497b4d3e2 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/GatewaySocket/OpCodes.cs @@ -0,0 +1,24 @@ +namespace Discord.API.Client.GatewaySocket +{ + public enum OpCodes : byte + { + /// C←S - Used to send most events. + Dispatch = 0, + /// C↔S - Used to keep the connection alive and measure latency. + Heartbeat = 1, + /// C→S - Used to associate a connection with a token and specify configuration. + Identify = 2, + /// C→S - Used to update client's status and current game id. + StatusUpdate = 3, + /// C→S - Used to join a particular voice channel. + VoiceStateUpdate = 4, + /// C→S - Used to ensure the server's voice server is alive. Only send this if voice connection fails or suddenly drops. + VoiceServerPing = 5, + /// C→S - Used to resume a connection after a redirect occurs. + Resume = 6, + /// C←S - Used to notify a client that they must reconnect to another gateway. + Reconnect = 7, + /// C→S - Used to request all members that were withheld by large_threshold + RequestGuildMembers = 8 + } +} diff --git a/discord.net/src/Discord.Net/API/Client/ISerializable.cs b/discord.net/src/Discord.Net/API/Client/ISerializable.cs new file mode 100644 index 000000000..d23dc3c6c --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/ISerializable.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace Discord.API.Client +{ + public interface ISerializable + { + void Write(BinaryWriter writer); + } +} diff --git a/discord.net/src/Discord.Net/API/Client/IWebSocketMessage.cs b/discord.net/src/Discord.Net/API/Client/IWebSocketMessage.cs new file mode 100644 index 000000000..6f6de535a --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/IWebSocketMessage.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client +{ + public interface IWebSocketMessage + { + int OpCode { get; } + object Payload { get; } + bool IsPrivate { get; } + } + public class WebSocketMessage + { + [JsonProperty("op")] + public int? Operation { get; set; } + [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] + public string Type { get; set; } + [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] + public uint? Sequence { get; set; } + [JsonProperty("d")] + public object Payload { get; set; } + + public WebSocketMessage() { } + public WebSocketMessage(IWebSocketMessage msg) + { + Operation = msg.OpCode; + Payload = msg.Payload; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/AcceptInvite.cs b/discord.net/src/Discord.Net/API/Client/Rest/AcceptInvite.cs new file mode 100644 index 000000000..2940c98ac --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/AcceptInvite.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class AcceptInviteRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"invite/{InviteId}"; + object IRestRequest.Payload => null; + + public string InviteId { get; set; } + + public AcceptInviteRequest(string inviteId) + { + InviteId = inviteId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/AckMessage.cs b/discord.net/src/Discord.Net/API/Client/Rest/AckMessage.cs new file mode 100644 index 000000000..4cf238b72 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/AckMessage.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class AckMessageRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}/ack"; + object IRestRequest.Payload => null; + + public ulong ChannelId { get; set; } + public ulong MessageId { get; set; } + + /*[JsonProperty("manual")] + public bool Manual { get; set; }*/ + + public AckMessageRequest(ulong channelId, ulong messageId) + { + ChannelId = channelId; + MessageId = messageId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs b/discord.net/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs new file mode 100644 index 000000000..44d08b4bf --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/AddChannelPermission.cs @@ -0,0 +1,29 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class AddChannelPermissionsRequest : IRestRequest + { + string IRestRequest.Method => "PUT"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; + object IRestRequest.Payload => this; + + public ulong ChannelId { get; set; } + + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong TargetId { get; set; } + [JsonProperty("type")] + public string TargetType { get; set; } + [JsonProperty("allow")] + public uint Allow { get; set; } + [JsonProperty("deny")] + public uint Deny { get; set; } + + public AddChannelPermissionsRequest(ulong channelId) + { + ChannelId = channelId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/AddGuildBan.cs b/discord.net/src/Discord.Net/API/Client/Rest/AddGuildBan.cs new file mode 100644 index 000000000..3e0b165f5 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/AddGuildBan.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class AddGuildBanRequest : IRestRequest + { + string IRestRequest.Method => "PUT"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}?delete-message-days={PruneDays}"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + public ulong UserId { get; set; } + + public int PruneDays { get; set; } = 0; + + public AddGuildBanRequest(ulong guildId, ulong userId) + { + GuildId = guildId; + UserId = userId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/CreateChannel.cs b/discord.net/src/Discord.Net/API/Client/Rest/CreateChannel.cs new file mode 100644 index 000000000..0d3309701 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/CreateChannel.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class CreateChannelRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; + object IRestRequest.Payload => this; + + public ulong GuildId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("type")] + public string Type { get; set; } + + public CreateChannelRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/CreateGuild.cs b/discord.net/src/Discord.Net/API/Client/Rest/CreateGuild.cs new file mode 100644 index 000000000..a18d2bee9 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/CreateGuild.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class CreateGuildRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"guilds"; + object IRestRequest.Payload => this; + + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("region")] + public string Region { get; set; } + [JsonProperty("icon")] + public string IconBase64 { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/CreateInvite.cs b/discord.net/src/Discord.Net/API/Client/Rest/CreateInvite.cs new file mode 100644 index 000000000..73f15c248 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/CreateInvite.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class CreateInviteRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/invites"; + object IRestRequest.Payload => this; + + public ulong ChannelId { get; set; } + + [JsonProperty("max_age")] + public int MaxAge { get; set; } = 1800; + [JsonProperty("max_uses")] + public int MaxUses { get; set; } = 0; + [JsonProperty("temporary")] + public bool IsTemporary { get; set; } = false; + [JsonProperty("xkcdpass")] + public bool WithXkcdPass { get; set; } = false; + /*[JsonProperty("validate")] + public bool Validate { get; set; }*/ + + public CreateInviteRequest(ulong channelId) + { + ChannelId = channelId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs b/discord.net/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs new file mode 100644 index 000000000..e1087dc36 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/CreatePrivateChannel.cs @@ -0,0 +1,16 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class CreatePrivateChannelRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"users/@me/channels"; + object IRestRequest.Payload => this; + + [JsonProperty("recipient_id"), JsonConverter(typeof(LongStringConverter))] + public ulong RecipientId { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/CreateRole.cs b/discord.net/src/Discord.Net/API/Client/Rest/CreateRole.cs new file mode 100644 index 000000000..3978c6aaa --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/CreateRole.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class CreateRoleRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + + public CreateRoleRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/DeleteChannel.cs b/discord.net/src/Discord.Net/API/Client/Rest/DeleteChannel.cs new file mode 100644 index 000000000..ae56934b5 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/DeleteChannel.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class DeleteChannelRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"channels/{ChannelId}"; + object IRestRequest.Payload => null; + + public ulong ChannelId { get; set; } + + public DeleteChannelRequest(ulong channelId) + { + ChannelId = channelId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/DeleteGuild.cs b/discord.net/src/Discord.Net/API/Client/Rest/DeleteGuild.cs new file mode 100644 index 000000000..44df5892e --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/DeleteGuild.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class DeleteGuildRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"guilds/{GuildId}"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + + public DeleteGuildRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/DeleteInvite.cs b/discord.net/src/Discord.Net/API/Client/Rest/DeleteInvite.cs new file mode 100644 index 000000000..4bfe1e0d7 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/DeleteInvite.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class DeleteInviteRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"invite/{InviteCode}"; + object IRestRequest.Payload => null; + + public string InviteCode { get; set; } + + public DeleteInviteRequest(string inviteCode) + { + InviteCode = inviteCode; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/DeleteMessage.cs b/discord.net/src/Discord.Net/API/Client/Rest/DeleteMessage.cs new file mode 100644 index 000000000..3f781a756 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/DeleteMessage.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class DeleteMessageRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; + object IRestRequest.Payload => null; + + public ulong ChannelId { get; set; } + public ulong MessageId { get; set; } + + public DeleteMessageRequest(ulong channelId, ulong messageId) + { + ChannelId = channelId; + MessageId = messageId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/DeleteRole.cs b/discord.net/src/Discord.Net/API/Client/Rest/DeleteRole.cs new file mode 100644 index 000000000..56faf3d33 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/DeleteRole.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class DeleteRoleRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + public ulong RoleId { get; set; } + + public DeleteRoleRequest(ulong guildId, ulong roleId) + { + GuildId = guildId; + RoleId = roleId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/Gateway.cs b/discord.net/src/Discord.Net/API/Client/Rest/Gateway.cs new file mode 100644 index 000000000..4d6ae015c --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/Gateway.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GatewayRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"gateway"; + object IRestRequest.Payload => null; + } + + public class GatewayResponse + { + [JsonProperty("url")] + public string Url { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/GetBans.cs b/discord.net/src/Discord.Net/API/Client/Rest/GetBans.cs new file mode 100644 index 000000000..714cdbaf8 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/GetBans.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetBansRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/bans"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + + public GetBansRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/GetInvite.cs b/discord.net/src/Discord.Net/API/Client/Rest/GetInvite.cs new file mode 100644 index 000000000..2531ac26a --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/GetInvite.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetInviteRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"invite/{InviteCode}"; + object IRestRequest.Payload => null; + + public string InviteCode { get; set; } + + public GetInviteRequest(string inviteCode) + { + InviteCode = inviteCode; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/GetInvites.cs b/discord.net/src/Discord.Net/API/Client/Rest/GetInvites.cs new file mode 100644 index 000000000..2b4f2f5fe --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/GetInvites.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetInvitesRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/invites"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + + public GetInvitesRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/GetMessages.cs b/discord.net/src/Discord.Net/API/Client/Rest/GetMessages.cs new file mode 100644 index 000000000..1beadb9a9 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/GetMessages.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; +using System.Text; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetMessagesRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint + { + get + { + StringBuilder query = new StringBuilder(); + this.AddQueryParam(query, "limit", Limit.ToString()); + if (RelativeDir != null) + this.AddQueryParam(query, RelativeDir, RelativeId.ToString()); + return $"channels/{ChannelId}/messages{query}"; + } + } + object IRestRequest.Payload => null; + + public ulong ChannelId { get; set; } + + public int Limit { get; set; } = 100; + public string RelativeDir { get; set; } = null; + public ulong RelativeId { get; set; } = 0; + + public GetMessagesRequest(ulong channelId) + { + ChannelId = channelId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs b/discord.net/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs new file mode 100644 index 000000000..df21cc203 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/GetVoiceRegions.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetVoiceRegionsRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"voice/regions"; + object IRestRequest.Payload => null; + } + + public class GetVoiceRegionsResponse + { + [JsonProperty("sample_hostname")] + public string Hostname { get; set; } + [JsonProperty("sample_port")] + public int Port { get; set; } + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("vip")] + public bool Vip { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/GetWidget.cs b/discord.net/src/Discord.Net/API/Client/Rest/GetWidget.cs new file mode 100644 index 000000000..0437a8b6b --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/GetWidget.cs @@ -0,0 +1,60 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetWidgetRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"servers/{GuildId}/widget.json"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + + public GetWidgetRequest(ulong guildId) + { + GuildId = guildId; + } + } + + public class GetWidgetResponse + { + public class Channel + { + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("position")] + public int Position { get; set; } + } + public class User : UserReference + { + [JsonProperty("avatar_url")] + public string AvatarUrl { get; set; } + [JsonProperty("status")] + public string Status { get; set; } + [JsonProperty("game")] + public UserGame Game { get; set; } + } + public class UserGame + { + [JsonProperty("id")] + public int Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + } + + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("channels")] + public Channel[] Channels { get; set; } + [JsonProperty("members")] + public MemberReference[] Members { get; set; } + [JsonProperty("instant_invite")] + public string InstantInviteUrl { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/KickMember.cs b/discord.net/src/Discord.Net/API/Client/Rest/KickMember.cs new file mode 100644 index 000000000..4808f8543 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/KickMember.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class KickMemberRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + public ulong UserId { get; set; } + + public KickMemberRequest(ulong guildId, ulong userId) + { + GuildId = guildId; + UserId = userId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/LeaveGuild.cs b/discord.net/src/Discord.Net/API/Client/Rest/LeaveGuild.cs new file mode 100644 index 000000000..99fd8cbe7 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/LeaveGuild.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class LeaveGuildRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"users/@me/guilds/{GuildId}"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + + public LeaveGuildRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/Login.cs b/discord.net/src/Discord.Net/API/Client/Rest/Login.cs new file mode 100644 index 000000000..f9c89c717 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/Login.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class LoginRequest : IRestRequest + { + string IRestRequest.Method => Email != null ? "POST" : "GET"; + string IRestRequest.Endpoint => $"auth/login"; + object IRestRequest.Payload => this; + + [JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)] + public string Email { get; set; } + [JsonProperty("password", NullValueHandling = NullValueHandling.Ignore)] + public string Password { get; set; } + } + + public class LoginResponse + { + [JsonProperty("token")] + public string Token { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/Logout.cs b/discord.net/src/Discord.Net/API/Client/Rest/Logout.cs new file mode 100644 index 000000000..9f4443c51 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/Logout.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class LogoutRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"auth/logout"; + object IRestRequest.Payload => null; + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/PruneMembers.cs b/discord.net/src/Discord.Net/API/Client/Rest/PruneMembers.cs new file mode 100644 index 000000000..e80498bb1 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/PruneMembers.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class PruneMembersRequest : IRestRequest + { + string IRestRequest.Method => IsSimulation ? "GET" : "POST"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/prune?days={Days}"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + + public int Days { get; set; } = 30; + public bool IsSimulation { get; set; } = false; + + public PruneMembersRequest(ulong guildId) + { + GuildId = guildId; + } + } + + public class PruneMembersResponse + { + [JsonProperty("pruned")] + public int Pruned { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs b/discord.net/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs new file mode 100644 index 000000000..b453cba49 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/RemoveChannelPermission.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class RemoveChannelPermissionsRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/permissions/{TargetId}"; + object IRestRequest.Payload => null; + + public ulong ChannelId { get; set; } + public ulong TargetId { get; set; } + + public RemoveChannelPermissionsRequest(ulong channelId, ulong targetId) + { + ChannelId = channelId; + TargetId = targetId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs b/discord.net/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs new file mode 100644 index 000000000..5a8f4f796 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/RemoveGuildBan.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class RemoveGuildBanRequest : IRestRequest + { + string IRestRequest.Method => "DELETE"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/bans/{UserId}"; + object IRestRequest.Payload => null; + + public ulong GuildId { get; set; } + public ulong UserId { get; set; } + + public RemoveGuildBanRequest(ulong guildId, ulong userId) + { + GuildId = guildId; + UserId = userId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/ReorderChannels.cs b/discord.net/src/Discord.Net/API/Client/Rest/ReorderChannels.cs new file mode 100644 index 000000000..c13f8b21c --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/ReorderChannels.cs @@ -0,0 +1,45 @@ +using Discord.API.Converters; +using Newtonsoft.Json; +using System.Linq; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class ReorderChannelsRequest : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/channels"; + object IRestRequest.Payload + { + get + { + int pos = StartPos; + return ChannelIds.Select(x => new Channel(x, pos++)); + } + } + + public class Channel + { + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("position")] + public int Position { get; set; } + + public Channel(ulong id, int position) + { + Id = id; + Position = position; + } + } + + public ulong GuildId { get; set; } + + public ulong[] ChannelIds { get; set; } = new ulong[0]; + public int StartPos { get; set; } = 0; + + public ReorderChannelsRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/ReorderRoles.cs b/discord.net/src/Discord.Net/API/Client/Rest/ReorderRoles.cs new file mode 100644 index 000000000..300176a76 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/ReorderRoles.cs @@ -0,0 +1,45 @@ +using Discord.API.Converters; +using Newtonsoft.Json; +using System.Linq; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class ReorderRolesRequest : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/roles"; + object IRestRequest.Payload + { + get + { + int pos = StartPos; + return RoleIds.Select(x => new Role(x, pos++)); + } + } + + public class Role + { + [JsonProperty("id"), JsonConverter(typeof(LongStringConverter))] + public ulong Id { get; set; } + [JsonProperty("position")] + public int Position { get; set; } + + public Role(ulong id, int pos) + { + Id = id; + Position = pos; + } + } + + public ulong GuildId { get; set; } + + public ulong[] RoleIds { get; set; } = new ulong[0]; + public int StartPos { get; set; } = 0; + + public ReorderRolesRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/SendFile.cs b/discord.net/src/Discord.Net/API/Client/Rest/SendFile.cs new file mode 100644 index 000000000..4b59e1a11 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/SendFile.cs @@ -0,0 +1,25 @@ +using Newtonsoft.Json; +using System.IO; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class SendFileRequest : IRestFileRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/messages"; + object IRestRequest.Payload => null; + string IRestFileRequest.Filename => Filename; + Stream IRestFileRequest.Stream => Stream; + + public ulong ChannelId { get; set; } + + public string Filename { get; set; } + public Stream Stream { get; set; } + + public SendFileRequest(ulong channelId) + { + ChannelId = channelId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/SendIsTyping.cs b/discord.net/src/Discord.Net/API/Client/Rest/SendIsTyping.cs new file mode 100644 index 000000000..4c56da0be --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/SendIsTyping.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class SendIsTypingRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/typing"; + object IRestRequest.Payload => null; + + public ulong ChannelId { get; set; } + + public SendIsTypingRequest(ulong channelId) + { + ChannelId = channelId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/SendMessage.cs b/discord.net/src/Discord.Net/API/Client/Rest/SendMessage.cs new file mode 100644 index 000000000..9caca991d --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/SendMessage.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class SendMessageRequest : IRestRequest + { + string IRestRequest.Method => "POST"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/messages"; + object IRestRequest.Payload => this; + + public ulong ChannelId { get; set; } + + [JsonProperty("content")] + public string Content { get; set; } + [JsonProperty("nonce", NullValueHandling = NullValueHandling.Ignore)] + public string Nonce { get; set; } + [JsonProperty("tts")] + public bool IsTTS { get; set; } + + public SendMessageRequest(ulong channelId) + { + ChannelId = channelId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/UpdateChannel.cs b/discord.net/src/Discord.Net/API/Client/Rest/UpdateChannel.cs new file mode 100644 index 000000000..86a35a605 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/UpdateChannel.cs @@ -0,0 +1,26 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateChannelRequest : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"channels/{ChannelId}"; + object IRestRequest.Payload => this; + + public ulong ChannelId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("topic")] + public string Topic { get; set; } + [JsonProperty("position")] + public int Position { get; set; } + + public UpdateChannelRequest(ulong channelId) + { + ChannelId = channelId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/UpdateGuild.cs b/discord.net/src/Discord.Net/API/Client/Rest/UpdateGuild.cs new file mode 100644 index 000000000..f36b18d9f --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/UpdateGuild.cs @@ -0,0 +1,33 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateGuildRequest : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"guilds/{GuildId}"; + object IRestRequest.Payload => this; + + public ulong GuildId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("region")] + public string Region { get; set; } + [JsonProperty("icon")] + public string IconBase64 { get; set; } + [JsonProperty("afk_channel_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? AFKChannelId { get; set; } + [JsonProperty("afk_timeout")] + public int AFKTimeout { get; set; } + [JsonProperty("splash")] + public object Splash { get; set; } + + public UpdateGuildRequest(ulong guildId) + { + GuildId = guildId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/UpdateMember.cs b/discord.net/src/Discord.Net/API/Client/Rest/UpdateMember.cs new file mode 100644 index 000000000..24a25ce02 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/UpdateMember.cs @@ -0,0 +1,33 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateMemberRequest : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/members/{UserId}"; + object IRestRequest.Payload => this; + + public ulong GuildId { get; set; } + public ulong UserId { get; set; } + + [JsonProperty("mute")] + public bool IsMuted { get; set; } + [JsonProperty("deaf")] + public bool IsDeafened { get; set; } + [JsonProperty("channel_id"), JsonConverter(typeof(NullableLongStringConverter))] + public ulong? VoiceChannelId { get; set; } + [JsonProperty("roles"), JsonConverter(typeof(LongStringArrayConverter))] + public ulong[] RoleIds { get; set; } + [JsonProperty("nick")] + public string Nickname { get; set; } + + public UpdateMemberRequest(ulong guildId, ulong userId) + { + GuildId = guildId; + UserId = userId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/UpdateMessage.cs b/discord.net/src/Discord.Net/API/Client/Rest/UpdateMessage.cs new file mode 100644 index 000000000..fc055b2bc --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/UpdateMessage.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateMessageRequest : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"channels/{ChannelId}/messages/{MessageId}"; + object IRestRequest.Payload => this; + + public ulong ChannelId { get; set; } + public ulong MessageId { get; set; } + + [JsonProperty("content")] + public string Content { get; set; } = ""; + + public UpdateMessageRequest(ulong channelId, ulong messageId) + { + ChannelId = channelId; + MessageId = messageId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/UpdateOwnNick.cs b/discord.net/src/Discord.Net/API/Client/Rest/UpdateOwnNick.cs new file mode 100644 index 000000000..f936d9506 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/UpdateOwnNick.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateOwnNick : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/members/@me/nick"; + object IRestRequest.Payload => this; + + public ulong GuildId { get; set; } + + [JsonProperty("nick")] + public string Nickname { get; set; } + + public UpdateOwnNick(ulong guildId, string nickname) + { + GuildId = guildId; + Nickname = nickname; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/UpdateProfile.cs b/discord.net/src/Discord.Net/API/Client/Rest/UpdateProfile.cs new file mode 100644 index 000000000..0f0cdb313 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/UpdateProfile.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateProfileRequest : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"users/@me"; + object IRestRequest.Payload => this; + + [JsonProperty("password")] + public string CurrentPassword { get; set; } + [JsonProperty("email")] + public string Email { get; set; } + [JsonProperty("new_password")] + public string Password { get; set; } + [JsonProperty("username")] + public string Username { get; set; } + [JsonProperty("avatar")] + public string AvatarBase64 { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/Rest/UpdateRole.cs b/discord.net/src/Discord.Net/API/Client/Rest/UpdateRole.cs new file mode 100644 index 000000000..68fb80437 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/Rest/UpdateRole.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class UpdateRoleRequest : IRestRequest + { + string IRestRequest.Method => "PATCH"; + string IRestRequest.Endpoint => $"guilds/{GuildId}/roles/{RoleId}"; + object IRestRequest.Payload => this; + + public ulong GuildId { get; set; } + public ulong RoleId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("permissions")] + public uint Permissions { get; set; } + [JsonProperty("hoist")] + public bool IsHoisted { get; set; } + [JsonProperty("mentionable")] + public bool IsMentionable { get; set; } + [JsonProperty("color")] + public uint Color { get; set; } + + public UpdateRoleRequest(ulong guildId, ulong roleId) + { + GuildId = guildId; + RoleId = roleId; + } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs new file mode 100644 index 000000000..349a8a28b --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/Heartbeat.cs @@ -0,0 +1,9 @@ +namespace Discord.API.Client.VoiceSocket +{ + public class HeartbeatCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.Heartbeat; + object IWebSocketMessage.Payload => EpochTime.GetMilliseconds(); + bool IWebSocketMessage.IsPrivate => false; + } +} diff --git a/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs new file mode 100644 index 000000000..fbb38b9d0 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/Identify.cs @@ -0,0 +1,21 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.VoiceSocket +{ + public class IdentifyCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.Identify; + object IWebSocketMessage.Payload => this; + bool IWebSocketMessage.IsPrivate => true; + + [JsonProperty("server_id"), JsonConverter(typeof(LongStringConverter))] + public ulong GuildId { get; set; } + [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] + public ulong UserId { get; set; } + [JsonProperty("session_id")] + public string SessionId { get; set; } + [JsonProperty("token")] + public string Token { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs new file mode 100644 index 000000000..d860efe45 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/SelectProtocol.cs @@ -0,0 +1,29 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.VoiceSocket +{ + public class SelectProtocolCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.SelectProtocol; + object IWebSocketMessage.Payload => this; + bool IWebSocketMessage.IsPrivate => false; + + public class Data + { + [JsonProperty("address")] + public string Address { get; set; } + [JsonProperty("port")] + public int Port { get; set; } + [JsonProperty("mode")] + public string Mode { get; set; } + } + [JsonProperty("protocol")] + public string Protocol { get; set; } = "udp"; + [JsonProperty("data")] + private Data ProtocolData { get; } = new Data(); + + public string ExternalAddress { get { return ProtocolData.Address; } set { ProtocolData.Address = value; } } + public int ExternalPort { get { return ProtocolData.Port; } set { ProtocolData.Port = value; } } + public string EncryptionMode { get { return ProtocolData.Mode; } set { ProtocolData.Mode = value; } } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs new file mode 100644 index 000000000..6022c4d58 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Commands/SetSpeaking.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.VoiceSocket +{ + public class SetSpeakingCommand : IWebSocketMessage + { + int IWebSocketMessage.OpCode => (int)OpCodes.Speaking; + object IWebSocketMessage.Payload => this; + bool IWebSocketMessage.IsPrivate => false; + + [JsonProperty("speaking")] + public bool IsSpeaking { get; set; } + [JsonProperty("delay")] + public int Delay { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs new file mode 100644 index 000000000..6fdced897 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/Ready.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.VoiceSocket +{ + public class ReadyEvent + { + [JsonProperty("ssrc")] + public uint SSRC { get; set; } + [JsonProperty("port")] + public ushort Port { get; set; } + [JsonProperty("modes")] + public string[] Modes { get; set; } + [JsonProperty("heartbeat_interval")] + public int HeartbeatInterval { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs new file mode 100644 index 000000000..042c5278d --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/SessionDescription.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Client.VoiceSocket +{ + public class SessionDescriptionEvent + { + [JsonProperty("secret_key")] + public byte[] SecretKey { get; set; } + [JsonProperty("mode")] + public string Mode { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs new file mode 100644 index 000000000..59268c4e6 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/VoiceSocket/Events/Speaking.cs @@ -0,0 +1,15 @@ +using Discord.API.Converters; +using Newtonsoft.Json; + +namespace Discord.API.Client.VoiceSocket +{ + public class SpeakingEvent + { + [JsonProperty("user_id"), JsonConverter(typeof(LongStringConverter))] + public ulong UserId { get; set; } + [JsonProperty("ssrc")] + public uint SSRC { get; set; } + [JsonProperty("speaking")] + public bool IsSpeaking { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Client/VoiceSocket/OpCodes.cs b/discord.net/src/Discord.Net/API/Client/VoiceSocket/OpCodes.cs new file mode 100644 index 000000000..e82ab5286 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Client/VoiceSocket/OpCodes.cs @@ -0,0 +1,18 @@ +namespace Discord.API.Client.VoiceSocket +{ + public enum OpCodes : byte + { + /// C→S - Used to associate a connection with a token. + Identify = 0, + /// C→S - Used to specify configuration. + SelectProtocol = 1, + /// C←S - Used to notify that the voice connection was successful and informs the client of available protocols. + Ready = 2, + /// C↔S - Used to keep the connection alive and measure latency. + Heartbeat = 3, + /// C←S - Used to provide an encryption key to the client. + SessionDescription = 4, + /// C↔S - Used to inform that a certain user is speaking. + Speaking = 5 + } +} diff --git a/discord.net/src/Discord.Net/API/Converters.cs b/discord.net/src/Discord.Net/API/Converters.cs new file mode 100644 index 000000000..5d80ca99f --- /dev/null +++ b/discord.net/src/Discord.Net/API/Converters.cs @@ -0,0 +1,89 @@ +using System; +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Discord.API.Converters +{ + public class LongStringConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + => objectType == typeof(ulong); + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + => ((string)reader.Value).ToId(); + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + => writer.WriteValue(((ulong)value).ToIdString()); + } + + public class NullableLongStringConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + => objectType == typeof(ulong?); + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + => ((string)reader.Value).ToNullableId(); + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + => writer.WriteValue(((ulong?)value).ToIdString()); + } + + /*public class LongStringEnumerableConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable); + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + List result = new List(); + if (reader.TokenType == JsonToken.StartArray) + { + reader.Read(); + while (reader.TokenType != JsonToken.EndArray) + { + result.Add(IdConvert.ToLong((string)reader.Value)); + reader.Read(); + } + } + return result; + } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + writer.WriteNull(); + else + { + writer.WriteStartArray(); + foreach (var v in (IEnumerable)value) + writer.WriteValue(IdConvert.ToString(v)); + writer.WriteEndArray(); + } + } + }*/ + + internal class LongStringArrayConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType == typeof(IEnumerable); + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var result = new List(); + if (reader.TokenType == JsonToken.StartArray) + { + reader.Read(); + while (reader.TokenType != JsonToken.EndArray) + { + result.Add(((string)reader.Value).ToId()); + reader.Read(); + } + } + return result.ToArray(); + } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value == null) + writer.WriteNull(); + else + { + writer.WriteStartArray(); + var a = (ulong[])value; + for (int i = 0; i < a.Length; i++) + writer.WriteValue(a[i].ToIdString()); + writer.WriteEndArray(); + } + } + } +} diff --git a/discord.net/src/Discord.Net/API/Extensions.cs b/discord.net/src/Discord.Net/API/Extensions.cs new file mode 100644 index 000000000..77d25fe4e --- /dev/null +++ b/discord.net/src/Discord.Net/API/Extensions.cs @@ -0,0 +1,19 @@ +using System; +using System.Text; + +namespace Discord.API +{ + internal static class RestRequestExtensions + { + public static void AddQueryParam(this IRestRequest request, StringBuilder builder, string name, string value) + { + if (builder.Length == 0) + builder.Append('?'); + else + builder.Append('&'); + builder.Append(Uri.EscapeDataString(name)); + builder.Append('='); + builder.Append(Uri.EscapeDataString(value)); + } + } +} diff --git a/discord.net/src/Discord.Net/API/IRestRequest.cs b/discord.net/src/Discord.Net/API/IRestRequest.cs new file mode 100644 index 000000000..af520370d --- /dev/null +++ b/discord.net/src/Discord.Net/API/IRestRequest.cs @@ -0,0 +1,25 @@ +using System.IO; + +namespace Discord.API +{ + public interface IRestRequest + { + string Method { get; } + string Endpoint { get; } + object Payload { get; } + } + public interface IRestRequest : IRestRequest + where ResponseT : class + { + } + + public interface IRestFileRequest : IRestRequest + { + string Filename { get; } + Stream Stream { get; } + } + public interface IRestFileRequest : IRestFileRequest, IRestRequest + where ResponseT : class + { + } +} diff --git a/discord.net/src/Discord.Net/API/Status/Common/StatusResult.cs b/discord.net/src/Discord.Net/API/Status/Common/StatusResult.cs new file mode 100644 index 000000000..74728c578 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Status/Common/StatusResult.cs @@ -0,0 +1,80 @@ +using Newtonsoft.Json; +using System; + +namespace Discord.API.Status +{ + public class StatusResult + { + public class PageData + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("url")] + public string Url { get; set; } + [JsonProperty("updated_at")] + public DateTime? UpdatedAt { get; set; } + } + + public class IncidentData + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("page_id")] + public string PageId { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("status")] + public string Status { get; set; } + [JsonProperty("shortlink")] + public string Shortlink { get; set; } + [JsonProperty("impact")] + public string Impact { get; set; } + + [JsonProperty("created_at")] + public DateTime CreatedAt { get; set; } + [JsonProperty("updated_at")] + public DateTime UpdatedAt { get; set; } + [JsonProperty("monitoring_at")] + public DateTime? MonitoringAt { get; set; } + [JsonProperty("resolved_at")] + public DateTime? ResolvedAt { get; set; } + [JsonProperty("scheduled_for")] + public DateTime StartTime { get; set; } + [JsonProperty("scheduled_until")] + public DateTime EndTime { get; set; } + + [JsonProperty("incident_updates")] + public IncidentUpdateData[] Updates { get; set; } + } + + public class IncidentUpdateData + { + [JsonProperty("id")] + public string Id { get; set; } + [JsonProperty("incident_id")] + public string IncidentId { get; set; } + [JsonProperty("status")] + public string Status { get; set; } + [JsonProperty("body")] + public string Body { get; set; } + + [JsonProperty("created_at")] + public DateTime CreatedAt { get; set; } + [JsonProperty("updated_at")] + public DateTime? UpdatedAt { get; set; } + [JsonProperty("display_at")] + public DateTime? DisplayAt { get; set; } + + } + + [JsonProperty("page")] + public PageData Page { get; set; } + [JsonProperty("scheduled_maintenances")] + public IncidentData[] ScheduledMaintenances { get; set; } + [JsonProperty("incidents")] + public IncidentData[] Incidents { get; set; } + } +} diff --git a/discord.net/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs b/discord.net/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs new file mode 100644 index 000000000..639f85f08 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Status/Rest/ActiveMaintenances.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Status.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetActiveMaintenancesRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"scheduled-maintenances/active.json"; + object IRestRequest.Payload => null; + } +} diff --git a/discord.net/src/Discord.Net/API/Status/Rest/AllIncidents.cs b/discord.net/src/Discord.Net/API/Status/Rest/AllIncidents.cs new file mode 100644 index 000000000..9575bbd43 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Status/Rest/AllIncidents.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Status.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetAllIncidentsRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"incidents.json"; + object IRestRequest.Payload => null; + } +} diff --git a/discord.net/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs b/discord.net/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs new file mode 100644 index 000000000..3cff11c23 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Status/Rest/UnresolvedIncidents.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Status.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetUnresolvedIncidentsRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"incidents/unresolved.json"; + object IRestRequest.Payload => null; + } +} diff --git a/discord.net/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs b/discord.net/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs new file mode 100644 index 000000000..803a8a630 --- /dev/null +++ b/discord.net/src/Discord.Net/API/Status/Rest/UpcomingMaintenances.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Status.Rest +{ + [JsonObject(MemberSerialization.OptIn)] + public class GetUpcomingMaintenancesRequest : IRestRequest + { + string IRestRequest.Method => "GET"; + string IRestRequest.Endpoint => $"scheduled-maintenances/upcoming.json"; + object IRestRequest.Payload => null; + } +} diff --git a/discord.net/src/Discord.Net/Discord.Net.xproj b/discord.net/src/Discord.Net/Discord.Net.xproj new file mode 100644 index 000000000..e26e53036 --- /dev/null +++ b/discord.net/src/Discord.Net/Discord.Net.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + acfb060b-ec8a-4926-b293-04c01e17ee23 + Discord + ..\..\artifacts\obj\$(MSBuildProjectName) + .\bin\ + + + 2.0 + + + True + + + \ No newline at end of file diff --git a/discord.net/src/Discord.Net/DiscordClient.Events.cs b/discord.net/src/Discord.Net/DiscordClient.Events.cs new file mode 100644 index 000000000..7ffb861f1 --- /dev/null +++ b/discord.net/src/Discord.Net/DiscordClient.Events.cs @@ -0,0 +1,108 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Discord +{ + public partial class DiscordClient + { + public event EventHandler Ready = delegate { }; + //public event EventHandler LoggedOut = delegate { }; + public event EventHandler ChannelCreated = delegate { }; + public event EventHandler ChannelDestroyed = delegate { }; + public event EventHandler ChannelUpdated = delegate { }; + public event EventHandler MessageAcknowledged = delegate { }; + public event EventHandler MessageDeleted = delegate { }; + public event EventHandler MessageReceived = delegate { }; + public event EventHandler MessageSent = delegate { }; + public event EventHandler MessageUpdated = delegate { }; + public event EventHandler ProfileUpdated = delegate { }; + public event EventHandler RoleCreated = delegate { }; + public event EventHandler RoleUpdated = delegate { }; + public event EventHandler RoleDeleted = delegate { }; + public event EventHandler JoinedServer = delegate { }; + public event EventHandler LeftServer = delegate { }; + public event EventHandler ServerAvailable = delegate { }; + public event EventHandler ServerUpdated = delegate { }; + public event EventHandler ServerUnavailable = delegate { }; + public event EventHandler UserBanned = delegate { }; + public event EventHandler UserIsTyping = delegate { }; + public event EventHandler UserJoined = delegate { }; + public event EventHandler UserLeft = delegate { }; + public event EventHandler UserUpdated = delegate { }; + public event EventHandler UserUnbanned = delegate { }; + + private void OnReady() + => OnEvent(Ready); + /*private void OnLoggedOut(bool wasUnexpected, Exception ex) + => OnEvent(LoggedOut, new DisconnectedEventArgs(wasUnexpected, ex));*/ + + private void OnChannelCreated(Channel channel) + => OnEvent(ChannelCreated, new ChannelEventArgs(channel)); + private void OnChannelDestroyed(Channel channel) + => OnEvent(ChannelDestroyed, new ChannelEventArgs(channel)); + private void OnChannelUpdated(Channel before, Channel after) + => OnEvent(ChannelUpdated, new ChannelUpdatedEventArgs(before, after)); + + private void OnMessageAcknowledged(Message msg) + => OnEvent(MessageAcknowledged, new MessageEventArgs(msg)); + private void OnMessageDeleted(Message msg) + => OnEvent(MessageDeleted, new MessageEventArgs(msg)); + private void OnMessageReceived(Message msg) + => OnEvent(MessageReceived, new MessageEventArgs(msg)); + internal void OnMessageSent(Message msg) + => OnEvent(MessageSent, new MessageEventArgs(msg)); + private void OnMessageUpdated(Message before, Message after) + => OnEvent(MessageUpdated, new MessageUpdatedEventArgs(before, after)); + + private void OnProfileUpdated(Profile before, Profile after) + => OnEvent(ProfileUpdated, new ProfileUpdatedEventArgs(before, after)); + + private void OnRoleCreated(Role role) + => OnEvent(RoleCreated, new RoleEventArgs(role)); + private void OnRoleDeleted(Role role) + => OnEvent(RoleDeleted, new RoleEventArgs(role)); + private void OnRoleUpdated(Role before, Role after) + => OnEvent(RoleUpdated, new RoleUpdatedEventArgs(before, after)); + + private void OnJoinedServer(Server server) + => OnEvent(JoinedServer, new ServerEventArgs(server)); + private void OnLeftServer(Server server) + => OnEvent(LeftServer, new ServerEventArgs(server)); + private void OnServerAvailable(Server server) + => OnEvent(ServerAvailable, new ServerEventArgs(server)); + private void OnServerUpdated(Server before, Server after) + => OnEvent(ServerUpdated, new ServerUpdatedEventArgs(before, after)); + private void OnServerUnavailable(Server server) + => OnEvent(ServerUnavailable, new ServerEventArgs(server)); + + private void OnUserBanned(User user) + => OnEvent(UserBanned, new UserEventArgs(user)); + private void OnUserIsTypingUpdated(Channel channel, User user) + => OnEvent(UserIsTyping, new ChannelUserEventArgs(channel, user)); + private void OnUserJoined(User user) + => OnEvent(UserJoined, new UserEventArgs(user)); + private void OnUserLeft(User user) + => OnEvent(UserLeft, new UserEventArgs(user)); + private void OnUserUnbanned(User user) + => OnEvent(UserUnbanned, new UserEventArgs(user)); + private void OnUserUpdated(User before, User after) + => OnEvent(UserUpdated, new UserUpdatedEventArgs(before, after)); + + private void OnEvent(EventHandler handler, T eventArgs, [CallerMemberName] string callerName = null) + { + try { handler(this, eventArgs); } + catch (Exception ex) + { + Logger.Error($"{callerName.Substring(2)}'s handler encountered an error", ex); + } + } + private void OnEvent(EventHandler handler, [CallerMemberName] string callerName = null) + { + try { handler(this, EventArgs.Empty); } + catch (Exception ex) + { + Logger.Error($"{callerName.Substring(2)}'s handler encountered an error", ex); + } + } + } +} diff --git a/discord.net/src/Discord.Net/DiscordClient.cs b/discord.net/src/Discord.Net/DiscordClient.cs new file mode 100644 index 000000000..81560d0ef --- /dev/null +++ b/discord.net/src/Discord.Net/DiscordClient.cs @@ -0,0 +1,1182 @@ +using Discord.API.Client.GatewaySocket; +using Discord.API.Client.Rest; +using Discord.Logging; +using Discord.Net; +using Discord.Net.Rest; +using Discord.Net.WebSockets; +using Newtonsoft.Json; +using Nito.AsyncEx; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord +{ + /// Provides a connection to the DiscordApp service. + public partial class DiscordClient : IDisposable + { + private readonly AsyncLock _connectionLock; + private readonly ManualResetEvent _disconnectedEvent; + private readonly ManualResetEventSlim _connectedEvent; + private readonly TaskManager _taskManager; + private readonly ServiceCollection _services; + private ConcurrentDictionary _servers; + private ConcurrentDictionary _channels; + private ConcurrentDictionary _privateChannels; //Key = RecipientId + private Dictionary _regions; + private Stopwatch _connectionStopwatch; + private ConcurrentQueue _largeServers; + + internal Logger Logger { get; } + + /// Gets the configuration object used to make this client. + public DiscordConfig Config { get; } + /// Gets the log manager. + public LogManager Log { get; } + /// Gets the internal RestClient for the Client API endpoint. + public RestClient ClientAPI { get; } + /// Gets the internal RestClient for the Status API endpoint. + public RestClient StatusAPI { get; } + /// Gets the internal WebSocket for the Gateway event stream. + public GatewaySocket GatewaySocket { get; } + /// Gets the queue used for outgoing messages, if enabled. + public MessageQueue MessageQueue { get; } + /// Gets the JSON serializer used by this client. + public JsonSerializer Serializer { get; } + + /// Gets the current connection state of this client. + public ConnectionState State { get; private set; } + /// Gets a cancellation token that triggers when the client is manually disconnected. + public CancellationToken CancelToken { get; private set; } + /// Gets the current logged-in user used in private channels. + internal User PrivateUser { get; private set; } + /// Gets information about the current logged-in account. + public Profile CurrentUser { get; private set; } + /// Gets the session id for the current connection. + public string SessionId { get; private set; } + /// Gets the status of the current user. + public UserStatus Status { get; private set; } + /// Gets the game the current user is displayed as playing. + public Game CurrentGame { get; private set; } + + /// Gets a collection of all extensions added to this DiscordClient. + public IEnumerable Services => _services; + /// Gets a collection of all servers this client is a member of. + public IEnumerable Servers => _servers.Select(x => x.Value); + /// Gets a collection of all private channels this client is a member of. + public IEnumerable PrivateChannels => _privateChannels.Select(x => x.Value); + /// Gets a collection of all voice regions currently offered by Discord. + public IEnumerable Regions => _regions.Select(x => x.Value); + + /// Initializes a new instance of the DiscordClient class. + public DiscordClient(Action configFunc) + : this(ProcessConfig(configFunc)) + { + } + private static DiscordConfigBuilder ProcessConfig(Action func) + { + var config = new DiscordConfigBuilder(); + func(config); + return config; + } + + /// Initializes a new instance of the DiscordClient class. + public DiscordClient() + : this(new DiscordConfigBuilder()) + { + } + /// Initializes a new instance of the DiscordClient class. + public DiscordClient(DiscordConfigBuilder builder) + : this(builder.Build()) + { + if (builder.LogHandler != null) + Log.Message += builder.LogHandler; + } + /// Initializes a new instance of the DiscordClient class. + public DiscordClient(DiscordConfig config) + { + Config = config; + + State = (int)ConnectionState.Disconnected; + Status = UserStatus.Online; + + //Logging + Log = new LogManager(this); + Logger = Log.CreateLogger("Discord"); + if (config.LogLevel >= LogSeverity.Verbose) + _connectionStopwatch = new Stopwatch(); + + //Async + _taskManager = new TaskManager(Cleanup); + _connectionLock = new AsyncLock(); + _disconnectedEvent = new ManualResetEvent(true); + _connectedEvent = new ManualResetEventSlim(false); + CancelToken = new CancellationToken(true); + + //Cache + //ConcurrentLevel = 2 (only REST and WebSocket can add/remove) + _servers = new ConcurrentDictionary(2, 0); + _channels = new ConcurrentDictionary(2, 0); + _privateChannels = new ConcurrentDictionary(2, 0); + _largeServers = new ConcurrentQueue(); + + //Serialization + Serializer = new JsonSerializer(); + Serializer.DateTimeZoneHandling = DateTimeZoneHandling.Utc; +#if TEST_RESPONSES + Serializer.CheckAdditionalContent = true; + Serializer.MissingMemberHandling = MissingMemberHandling.Error; +#else + Serializer.CheckAdditionalContent = false; + Serializer.MissingMemberHandling = MissingMemberHandling.Ignore; +#endif + Serializer.Error += (s, e) => + { + e.ErrorContext.Handled = true; + Logger.Error("Serialization Failed", e.ErrorContext.Error); + }; + + //Networking + ClientAPI = new JsonRestClient(Config, DiscordConfig.ClientAPIUrl, Log.CreateLogger("ClientAPI")); + StatusAPI = new JsonRestClient(Config, DiscordConfig.StatusAPIUrl, Log.CreateLogger("StatusAPI")); + GatewaySocket = new GatewaySocket(Config, Serializer, Log.CreateLogger("Gateway")); + + //GatewaySocket.Disconnected += (s, e) => OnDisconnected(e.WasUnexpected, e.Exception); + GatewaySocket.ReceivedDispatch += (s, e) => OnReceivedEvent(e); + + MessageQueue = new MessageQueue(ClientAPI, Log.CreateLogger("MessageQueue")); + + //Extensibility + _services = new ServiceCollection(this); + } + + /// Connects to the Discord server with the provided email and password. + /// Returns a token that can be optionally stored to speed up future connections. + public async Task Connect(string email, string password, string token = null) + { + if (email == null) throw new ArgumentNullException(email); + if (password == null) throw new ArgumentNullException(password); + + await BeginConnect(email, password, null).ConfigureAwait(false); + return ClientAPI.Token; + } + /// Connects to the Discord server with the provided token. + public async Task Connect(string token) + { + if (token == null) throw new ArgumentNullException(token); + + await BeginConnect(null, null, token).ConfigureAwait(false); + } + + private async Task BeginConnect(string email, string password, string token = null) + { + try + { + using (await _connectionLock.LockAsync().ConfigureAwait(false)) + { + await Disconnect().ConfigureAwait(false); + _taskManager.ClearException(); + + Stopwatch stopwatch = null; + if (Config.LogLevel >= LogSeverity.Verbose) + { + _connectionStopwatch.Restart(); + stopwatch = Stopwatch.StartNew(); + } + State = ConnectionState.Connecting; + _disconnectedEvent.Reset(); + + var cancelSource = new CancellationTokenSource(); + CancelToken = cancelSource.Token; + ClientAPI.CancelToken = CancelToken; + StatusAPI.CancelToken = CancelToken; + + await Login(email, password, token).ConfigureAwait(false); + await GatewaySocket.Connect(ClientAPI, CancelToken).ConfigureAwait(false); + + var tasks = new[] { CancelToken.Wait(), LargeServerDownloader(CancelToken) } + .Concat(MessageQueue.Run(CancelToken)); + + await _taskManager.Start(tasks, cancelSource).ConfigureAwait(false); + GatewaySocket.WaitForConnection(CancelToken); + + if (Config.LogLevel >= LogSeverity.Verbose) + { + stopwatch.Stop(); + double seconds = Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); + Logger.Verbose($"Handshake + Ready took {seconds} sec"); + } + } + } + catch (Exception ex) + { + await _taskManager.SignalError(ex).ConfigureAwait(false); + throw; + } + } + private async Task Login(string email = null, string password = null, string token = null) + { + string tokenPath = null, oldToken = null; + byte[] cacheKey = null; + + //Get Token + if (email != null && Config.CacheDir != null) + { + tokenPath = GetTokenCachePath(email); + if (token == null && password != null) + { + Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password, + new byte[] { 0x5A, 0x2A, 0xF8, 0xCF, 0x78, 0xD3, 0x7D, 0x0D }); + cacheKey = deriveBytes.GetBytes(16); + + oldToken = LoadToken(tokenPath, cacheKey); + token = oldToken; + } + } + + ClientAPI.Token = token; + if (email != null && password != null) + { + var request = new LoginRequest() { Email = email, Password = password }; + var response = await ClientAPI.Send(request).ConfigureAwait(false); + token = response.Token; + if (Config.CacheDir != null && token != oldToken && tokenPath != null) + SaveToken(tokenPath, cacheKey, token); + ClientAPI.Token = token; + } + + //Cache other stuff + var regionsResponse = (await ClientAPI.Send(new GetVoiceRegionsRequest()).ConfigureAwait(false)); + _regions = regionsResponse.Select(x => new Region(x.Id, x.Name, x.Hostname, x.Port, x.Vip)) + .ToDictionary(x => x.Id); + } + private void EndConnect() + { + if (State == ConnectionState.Connecting) + { + State = ConnectionState.Connected; + _connectedEvent.Set(); + + if (Config.LogLevel >= LogSeverity.Verbose) + { + _connectionStopwatch.Stop(); + double seconds = Math.Round(_connectionStopwatch.ElapsedTicks / (double)TimeSpan.TicksPerSecond, 2); + Logger.Verbose($"Connection took {seconds} sec"); + } + + SendStatus(); + OnReady(); + } + } + + /// Disconnects from the Discord server, canceling any pending requests. + public Task Disconnect() => _taskManager.Stop(true); + private async Task Cleanup() + { + var oldState = State; + State = ConnectionState.Disconnecting; + + if (oldState == ConnectionState.Connected) + { + try { await ClientAPI.Send(new LogoutRequest()).ConfigureAwait(false); } + catch (OperationCanceledException) { } + } + + ulong serverId; + while (_largeServers.TryDequeue(out serverId)) { } + + MessageQueue.Clear(); + + await GatewaySocket.Disconnect().ConfigureAwait(false); + ClientAPI.Token = null; + + _servers.Clear(); + _channels.Clear(); + _privateChannels.Clear(); + + PrivateUser = null; + CurrentUser = null; + + State = (int)ConnectionState.Disconnected; + _connectedEvent.Reset(); + _disconnectedEvent.Set(); + } + + public void SetStatus(UserStatus status) + { + if (status == null) throw new ArgumentNullException(nameof(status)); + if (status != UserStatus.Online && status != UserStatus.Idle) + throw new ArgumentException($"Invalid status, must be {UserStatus.Online} or {UserStatus.Idle}", nameof(status)); + + Status = status; + SendStatus(); + } + public void SetGame(Game game) + { + CurrentGame = game; + SendStatus(); + } + public void SetGame(string game) + { + CurrentGame = new Game(game); + SendStatus(); + } + public void SetGame(string game, GameType type, string url) + { + CurrentGame = new Game(game, type, url); + SendStatus(); + } + private void SendStatus() + { + PrivateUser.Status = Status; + PrivateUser.CurrentGame = CurrentGame; + foreach (var server in Servers) + { + var current = server.CurrentUser; + if (current != null) + { + current.Status = Status; + current.CurrentGame = CurrentGame; + } + } + var socket = GatewaySocket; + if (socket != null) + socket.SendUpdateStatus(Status == UserStatus.Idle ? EpochTime.GetMilliseconds() - (10 * 60 * 1000) : (long?)null, CurrentGame); + } + + #region Channels + internal void AddChannel(Channel channel) + { + _channels.GetOrAdd(channel.Id, channel); + } + private Channel RemoveChannel(ulong id) + { + Channel channel; + if (_channels.TryRemove(id, out channel)) + { + if (channel.IsPrivate) + _privateChannels.TryRemove(channel.Recipient.Id, out channel); + else + channel.Server.RemoveChannel(id); + } + return channel; + } + public Channel GetChannel(ulong id) + { + Channel channel; + _channels.TryGetValue(id, out channel); + return channel; + } + + private Channel AddPrivateChannel(ulong id, ulong recipientId) + { + Channel channel; + if (_channels.TryGetOrAdd(id, x => new Channel(this, x, new User(this, recipientId, null)), out channel)) + _privateChannels[recipientId] = channel; + return channel; + } + internal Channel GetPrivateChannel(ulong recipientId) + { + Channel channel; + _privateChannels.TryGetValue(recipientId, out channel); + return channel; + } + internal Task CreatePMChannel(User user) + => CreatePrivateChannel(user.Id); + public async Task CreatePrivateChannel(ulong userId) + { + var channel = GetPrivateChannel(userId); + if (channel != null) return channel; + + var request = new CreatePrivateChannelRequest() { RecipientId = userId }; + var response = await ClientAPI.Send(request).ConfigureAwait(false); + + channel = AddPrivateChannel(response.Id, userId); + channel.Update(response); + return channel; + } + #endregion + + #region Invites + /// Gets more info about the provided invite code. + /// Supported formats: inviteCode, xkcdCode, https://discord.gg/inviteCode, https://discord.gg/xkcdCode + /// The invite object if found, null if not. + public async Task GetInvite(string inviteIdOrXkcd) + { + if (inviteIdOrXkcd == null) throw new ArgumentNullException(nameof(inviteIdOrXkcd)); + + //Remove trailing slash + if (inviteIdOrXkcd.Length > 0 && inviteIdOrXkcd[inviteIdOrXkcd.Length - 1] == '/') + inviteIdOrXkcd = inviteIdOrXkcd.Substring(0, inviteIdOrXkcd.Length - 1); + //Remove leading URL + int index = inviteIdOrXkcd.LastIndexOf('/'); + if (index >= 0) + inviteIdOrXkcd = inviteIdOrXkcd.Substring(index + 1); + + try + { + var response = await ClientAPI.Send(new GetInviteRequest(inviteIdOrXkcd)).ConfigureAwait(false); + var invite = new Invite(this, response.Code, response.XkcdPass); + invite.Update(response); + return invite; + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) + { + return null; + } + } + #endregion + + #region Regions + public Region GetRegion(string id) + { + Region region; + if (_regions.TryGetValue(id, out region)) + return region; + else + return new Region(id, id, "", 0, false); + } + #endregion + + #region Servers + private Server AddServer(ulong id) + => _servers.GetOrAdd(id, x => new Server(this, x)); + private Server RemoveServer(ulong id) + { + Server server; + if (_servers.TryRemove(id, out server)) + { + foreach (var channel in server.AllChannels) + RemoveChannel(channel.Id); + } + return server; + } + + public Server GetServer(ulong id) + { + Server server; + _servers.TryGetValue(id, out server); + return server; + } + public IEnumerable FindServers(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + return _servers.Select(x => x.Value).Find(name); + } + + /// Creates a new server with the provided name and region. + public async Task CreateServer(string name, Region region, ImageType iconType = ImageType.None, Stream icon = null) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (region == null) throw new ArgumentNullException(nameof(region)); + + var request = new CreateGuildRequest() + { + Name = name, + Region = region.Id, + IconBase64 = icon.Base64(iconType, null) + }; + var response = await ClientAPI.Send(request).ConfigureAwait(false); + + var server = AddServer(response.Id); + server.Update(response); + return server; + } + #endregion + + #region Gateway Events + private void OnReceivedEvent(WebSocketEventEventArgs e) + { + try + { + switch (e.Type) + { + //Global + case "READY": + { + //TODO: None of this is really threadsafe - should only replace the cache collections when they have been fully populated + + var data = e.Payload.ToObject(Serializer); + + //ConcurrencyLevel = 2 (only REST and WebSocket can add/remove) + _servers = new ConcurrentDictionary(2, (int)(data.Guilds.Length * 1.05)); + _channels = new ConcurrentDictionary(2, (int)(data.Guilds.Length * 2 * 1.05)); + _privateChannels = new ConcurrentDictionary(2, (int)(data.PrivateChannels.Length * 1.05)); + + SessionId = data.SessionId; + PrivateUser = new User(this, data.User.Id, null); + PrivateUser.Update(data.User); + CurrentUser = new Profile(this, data.User.Id); + CurrentUser.Update(data.User); + + for (int i = 0; i < data.Guilds.Length; i++) + { + var model = data.Guilds[i]; + if (model.Unavailable != true) + { + var server = AddServer(model.Id); + server.Update(model); + } + if (model.IsLarge) + _largeServers.Enqueue(model.Id); + } + for (int i = 0; i < data.PrivateChannels.Length; i++) + { + var model = data.PrivateChannels[i]; + var channel = AddPrivateChannel(model.Id, model.Recipient.Id); + channel.Update(model); + } + + EndConnect(); + } + break; + + //Servers + case "GUILD_CREATE": + { + var data = e.Payload.ToObject(Serializer); + if (data.Unavailable != true) + { + var server = AddServer(data.Id); + server.Update(data); + + if (data.Unavailable != false) + { + Logger.Info($"GUILD_CREATE: {server.Path}"); + OnJoinedServer(server); + } + else + Logger.Info($"GUILD_AVAILABLE: {server.Path}"); + + if (!data.IsLarge) + OnServerAvailable(server); + else + _largeServers.Enqueue(data.Id); + } + } + break; + case "GUILD_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.Id); + if (server != null) + { + var before = Config.EnablePreUpdateEvents ? server.Clone() : null; + server.Update(data); + Logger.Info($"GUILD_UPDATE: {server.Path}"); + OnServerUpdated(before, server); + } + else + Logger.Warning("GUILD_UPDATE referenced an unknown guild."); + } + break; + case "GUILD_DELETE": + { + var data = e.Payload.ToObject(Serializer); + Server server = RemoveServer(data.Id); + if (server != null) + { + if (data.Unavailable != true) + Logger.Info($"GUILD_DELETE: {server.Path}"); + else + Logger.Info($"GUILD_UNAVAILABLE: {server.Path}"); + + OnServerUnavailable(server); + if (data.Unavailable != true) + OnLeftServer(server); + } + else + Logger.Warning("GUILD_DELETE referenced an unknown guild."); + } + break; + + //Channels + case "CHANNEL_CREATE": + { + var data = e.Payload.ToObject(Serializer); + + Channel channel = null; + if (data.GuildId != null) + { + var server = GetServer(data.GuildId.Value); + if (server != null) + channel = server.AddChannel(data.Id, true); + else + Logger.Warning("CHANNEL_CREATE referenced an unknown guild."); + } + else + channel = AddPrivateChannel(data.Id, data.Recipient.Id); + if (channel != null) + { + channel.Update(data); + Logger.Info($"CHANNEL_CREATE: {channel.Path}"); + OnChannelCreated(channel); + } + } + break; + case "CHANNEL_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + var channel = GetChannel(data.Id); + if (channel != null) + { + var before = Config.EnablePreUpdateEvents ? channel.Clone() : null; + channel.Update(data); + Logger.Info($"CHANNEL_UPDATE: {channel.Path}"); + OnChannelUpdated(before, channel); + } + else + Logger.Warning("CHANNEL_UPDATE referenced an unknown channel."); + } + break; + case "CHANNEL_DELETE": + { + var data = e.Payload.ToObject(Serializer); + var channel = RemoveChannel(data.Id); + if (channel != null) + { + Logger.Info($"CHANNEL_DELETE: {channel.Path}"); + OnChannelDestroyed(channel); + } + else + Logger.Warning("CHANNEL_DELETE referenced an unknown channel."); + } + break; + + //Members + case "GUILD_MEMBER_ADD": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.AddUser(data.User.Id, true, true); + user.Update(data); + user.UpdateActivity(); + Logger.Info($"GUILD_MEMBER_ADD: {user.Path}"); + OnUserJoined(user); + } + else + Logger.Warning("GUILD_MEMBER_ADD referenced an unknown guild."); + } + break; + case "GUILD_MEMBER_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.GetUser(data.User.Id); + if (user != null) + { + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + Logger.Info($"GUILD_MEMBER_UPDATE: {user.Path}"); + OnUserUpdated(before, user); + } + else + Logger.Warning("GUILD_MEMBER_UPDATE referenced an unknown user."); + } + else + Logger.Warning("GUILD_MEMBER_UPDATE referenced an unknown guild."); + } + break; + case "GUILD_MEMBER_REMOVE": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.RemoveUser(data.User.Id); + if (user != null) + { + Logger.Info($"GUILD_MEMBER_REMOVE: {user.Path}"); + OnUserLeft(user); + } + else + Logger.Warning("GUILD_MEMBER_REMOVE referenced an unknown user."); + } + else + Logger.Warning("GUILD_MEMBER_REMOVE referenced an unknown guild."); + } + break; + case "GUILD_MEMBERS_CHUNK": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + foreach (var memberData in data.Members) + { + var user = server.AddUser(memberData.User.Id, true, false); + user.Update(memberData); + } + Logger.Verbose($"GUILD_MEMBERS_CHUNK: {data.Members.Length} users"); + + if (server.CurrentUserCount >= server.UserCount) //Finished downloading for there + OnServerAvailable(server); + } + else + Logger.Warning("GUILD_MEMBERS_CHUNK referenced an unknown guild."); + } + break; + + //Roles + case "GUILD_ROLE_CREATE": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var role = server.AddRole(data.Data.Id); + role.Update(data.Data, false); + Logger.Info($"GUILD_ROLE_CREATE: {role.Path}"); + OnRoleCreated(role); + } + else + Logger.Warning("GUILD_ROLE_CREATE referenced an unknown guild."); + } + break; + case "GUILD_ROLE_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var role = server.GetRole(data.Data.Id); + if (role != null) + { + var before = Config.EnablePreUpdateEvents ? role.Clone() : null; + role.Update(data.Data, true); + Logger.Info($"GUILD_ROLE_UPDATE: {role.Path}"); + OnRoleUpdated(before, role); + } + else + Logger.Warning("GUILD_ROLE_UPDATE referenced an unknown role."); + } + else + Logger.Warning("GUILD_ROLE_UPDATE referenced an unknown guild."); + } + break; + case "GUILD_ROLE_DELETE": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var role = server.RemoveRole(data.RoleId); + if (role != null) + { + Logger.Info($"GUILD_ROLE_DELETE: {role.Path}"); + OnRoleDeleted(role); + } + else + Logger.Warning("GUILD_ROLE_DELETE referenced an unknown role."); + } + else + Logger.Warning("GUILD_ROLE_DELETE referenced an unknown guild."); + } + break; + + //Bans + case "GUILD_BAN_ADD": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = server.GetUser(data.User.Id); + if (user != null) + { + Logger.Info($"GUILD_BAN_ADD: {user.Path}"); + OnUserBanned(user); + } + else + Logger.Warning("GUILD_BAN_ADD referenced an unknown user."); + } + else + Logger.Warning("GUILD_BAN_ADD referenced an unknown guild."); + } + break; + case "GUILD_BAN_REMOVE": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId.Value); + if (server != null) + { + var user = new User(this, data.User.Id, server); + user.Update(data.User); + Logger.Info($"GUILD_BAN_REMOVE: {user.Path}"); + OnUserUnbanned(user); + } + else + Logger.Warning("GUILD_BAN_REMOVE referenced an unknown guild."); + } + break; + + //Messages + case "MESSAGE_CREATE": + { + var data = e.Payload.ToObject(Serializer); + + Channel channel = GetChannel(data.ChannelId); + if (channel != null) + { + var user = channel.GetUserFast(data.Author.Id); + + if (user != null) + { + Message msg = null; + bool isAuthor = data.Author.Id == CurrentUser.Id; + //ulong nonce = 0; + + /*if (data.Author.Id == _privateUser.Id && Config.UseMessageQueue) + { + if (data.Nonce != null && ulong.TryParse(data.Nonce, out nonce)) + msg = _messages[nonce]; + }*/ + if (msg == null) + { + msg = channel.AddMessage(data.Id, user, data.Timestamp.Value); + //nonce = 0; + } + + //Remapped queued message + /*if (nonce != 0) + { + msg = _messages.Remap(nonce, data.Id); + msg.Id = data.Id; + RaiseMessageSent(msg); + }*/ + + msg.Update(data); + user.UpdateActivity(); + + Logger.Verbose($"MESSAGE_CREATE: {channel.Path} ({user.Name ?? "Unknown"})"); + OnMessageReceived(msg); + } + else + Logger.Warning("MESSAGE_CREATE referenced an unknown user."); + } + else + Logger.Warning("MESSAGE_CREATE referenced an unknown channel."); + } + break; + case "MESSAGE_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + var msg = channel.GetMessage(data.Id, data.Author?.Id); + var before = Config.EnablePreUpdateEvents ? msg.Clone() : null; + msg.Update(data); + Logger.Verbose($"MESSAGE_UPDATE: {channel.Path} ({data.Author?.Username ?? "Unknown"})"); + OnMessageUpdated(before, msg); + } + else + Logger.Warning("MESSAGE_UPDATE referenced an unknown channel."); + } + break; + case "MESSAGE_DELETE": + { + var data = e.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + var msg = channel.RemoveMessage(data.Id); + Logger.Verbose($"MESSAGE_DELETE: {channel.Path} ({msg.User?.Name ?? "Unknown"})"); + OnMessageDeleted(msg); + } + else + Logger.Warning("MESSAGE_DELETE referenced an unknown channel."); + } + break; + + //Statuses + case "PRESENCE_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + User user; + Server server; + if (data.GuildId == null) + { + server = null; + user = GetPrivateChannel(data.User.Id)?.Recipient; + } + else + { + server = GetServer(data.GuildId.Value); + if (server == null) + { + Logger.Warning("PRESENCE_UPDATE referenced an unknown server."); + break; + } + else + user = server.GetUser(data.User.Id); + } + + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + Logger.Debug($"PRESENCE_UPDATE: {user.Path}"); + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + OnUserUpdated(before, user); + } + /*else //Occurs when a user leaves a server + Logger.Warning("PRESENCE_UPDATE referenced an unknown user.");*/ + } + break; + case "TYPING_START": + { + var data = e.Payload.ToObject(Serializer); + var channel = GetChannel(data.ChannelId); + if (channel != null) + { + User user; + if (channel.IsPrivate) + { + if (channel.Recipient.Id == data.UserId) + user = channel.Recipient; + else + break; + } + else + user = channel.Server.GetUser(data.UserId); + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + Logger.Debug($"TYPING_START: {channel.Path} ({user.Name})"); + OnUserIsTypingUpdated(channel, user); + user.UpdateActivity(); + } + } + else + Logger.Warning("TYPING_START referenced an unknown channel."); + } + break; + + //Voice + case "VOICE_STATE_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + var server = GetServer(data.GuildId); + if (server != null) + { + var user = server.GetUser(data.UserId); + if (user != null) + { + if (Config.LogLevel == LogSeverity.Debug) + Logger.Debug($"VOICE_STATE_UPDATE: {user.Path}"); + var before = Config.EnablePreUpdateEvents ? user.Clone() : null; + user.Update(data); + //Logger.Verbose($"Voice Updated: {server.Name}/{user.Name}"); + OnUserUpdated(before, user); + } + /*else //Occurs when a user leaves a server + Logger.Warning("VOICE_STATE_UPDATE referenced an unknown user.");*/ + } + else + Logger.Warning("VOICE_STATE_UPDATE referenced an unknown server."); + } + break; + + //Settings + case "USER_UPDATE": + { + var data = e.Payload.ToObject(Serializer); + if (data.Id == CurrentUser.Id) + { + var before = Config.EnablePreUpdateEvents ? CurrentUser.Clone() : null; + CurrentUser.Update(data); + PrivateUser.Update(data); + foreach (var server in _servers) + server.Value.CurrentUser.Update(data); + Logger.Info($"USER_UPDATE"); + OnProfileUpdated(before, CurrentUser); + } + } + break; + + //Handled in GatewaySocket + case "RESUMED": + break; + + //Ignored + case "USER_SETTINGS_UPDATE": + case "GUILD_INTEGRATIONS_UPDATE": + case "VOICE_SERVER_UPDATE": + case "GUILD_EMOJIS_UPDATE": + case "MESSAGE_ACK": + Logger.Debug($"{e.Type} [Ignored]"); + break; + + //Others + default: + Logger.Warning($"Unknown message type: {e.Type}"); + break; + } + } + catch (Exception ex) + { + Logger.Error($"Error handling {e.Type} event", ex); + } + } + #endregion + + #region Services + public T AddService(T instance) + where T : class, IService + => _services.Add(instance); + public T AddService() + where T : class, IService, new() + => _services.Add(new T()); + public T GetService(bool isRequired = true) + where T : class, IService + => _services.Get(isRequired); + #endregion + + #region Async Wrapper + /// Blocking call that will execute the provided async method and wait until the client has been manually stopped. This is mainly intended for use in console applications. + public void ExecuteAndWait(Func asyncAction) + { + asyncAction().GetAwaiter().GetResult(); + _disconnectedEvent.WaitOne(); + } + /// Blocking call and wait until the client has been manually stopped. This is mainly intended for use in console applications. + public void Wait() + { + _disconnectedEvent.WaitOne(); + } + #endregion + + #region IDisposable + private bool _isDisposed = false; + + protected virtual void Dispose(bool isDisposing) + { + if (!_isDisposed) + { + if (isDisposing) + { + _disconnectedEvent.Dispose(); + _connectedEvent.Dispose(); + } + _isDisposed = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + + private Task LargeServerDownloader(CancellationToken cancelToken) + { + //Temporary hotfix to download all large guilds before raising READY + return Task.Run(async () => + { + try + { + const short batchSize = 50; + ulong[] serverIds = new ulong[batchSize]; + + while (!cancelToken.IsCancellationRequested && State == ConnectionState.Connecting) + await Task.Delay(1000, cancelToken).ConfigureAwait(false); + + while (!cancelToken.IsCancellationRequested && State == ConnectionState.Connected) + { + if (_largeServers.Count > 0) + { + int count = 0; + while (count < batchSize && _largeServers.TryDequeue(out serverIds[count])) + count++; + + if (count > 0) + GatewaySocket.SendRequestMembers(serverIds.Take(count), "", 0); + } + await Task.Delay(1250, cancelToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + + //Helpers + private string GetTokenCachePath(string email) + { + using (var md5 = MD5.Create()) + { + byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(email.ToLowerInvariant())); + StringBuilder filenameBuilder = new StringBuilder(); + for (int i = 0; i < data.Length; i++) + filenameBuilder.Append(data[i].ToString("x2")); + return Path.Combine(Config.CacheDir, filenameBuilder.ToString()); + } + } + private string LoadToken(string path, byte[] key) + { + if (File.Exists(path)) + { + try + { + using (var fileStream = File.Open(path, FileMode.Open)) + using (var aes = Aes.Create()) + { + byte[] iv = new byte[aes.BlockSize / 8]; + fileStream.Read(iv, 0, iv.Length); + aes.IV = iv; + aes.Key = key; + using (var cryptoStream = new CryptoStream(fileStream, aes.CreateDecryptor(), CryptoStreamMode.Read)) + { + byte[] tokenBuffer = new byte[64]; + int length = cryptoStream.Read(tokenBuffer, 0, tokenBuffer.Length); + return Encoding.UTF8.GetString(tokenBuffer, 0, length); + } + } + } + catch (Exception ex) + { + Logger.Warning("Failed to load cached token. Wrong/changed password?", ex); + } + } + return null; + } + private void SaveToken(string path, byte[] key, string token) + { + byte[] tokenBytes = Encoding.UTF8.GetBytes(token); + try + { + string parentDir = Path.GetDirectoryName(path); + if (!Directory.Exists(parentDir)) + Directory.CreateDirectory(parentDir); + + using (var fileStream = File.Open(path, FileMode.Create)) + using (var aes = Aes.Create()) + { + aes.GenerateIV(); + aes.Key = key; + using (var cryptoStream = new CryptoStream(fileStream, aes.CreateEncryptor(), CryptoStreamMode.Write)) + { + fileStream.Write(aes.IV, 0, aes.IV.Length); + cryptoStream.Write(tokenBytes, 0, tokenBytes.Length); + } + } + } + catch (Exception ex) + { + Logger.Warning("Failed to cache token", ex); + } + } + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net/DiscordConfig.cs b/discord.net/src/Discord.Net/DiscordConfig.cs new file mode 100644 index 000000000..c7a754812 --- /dev/null +++ b/discord.net/src/Discord.Net/DiscordConfig.cs @@ -0,0 +1,141 @@ +using System; +using System.IO; +using System.Reflection; +using System.Text; + +namespace Discord +{ + public class DiscordConfigBuilder + { + //Global + + /// Gets or sets name of your application, used both for the token cache directory and user agent. + public string AppName { get; set; } = null; + /// Gets or sets url to your application, used in the user agent. + public string AppUrl { get; set; } = null; + /// Gets or sets the version of your application, used in the user agent. + public string AppVersion { get; set; } = null; + + /// Gets or sets the minimum log level severity that will be sent to the LogMessage event. + public LogSeverity LogLevel { get; set; } = LogSeverity.Info; + + //WebSocket + + /// Gets or sets the time (in milliseconds) to wait for the websocket to connect and initialize. + public int ConnectionTimeout { get; set; } = 30000; + /// Gets or sets the time (in milliseconds) to wait after an unexpected disconnect before reconnecting. + public int ReconnectDelay { get; set; } = 1000; + /// Gets or sets the time (in milliseconds) to wait after an reconnect fails before retrying. + public int FailedReconnectDelay { get; set; } = 15000; + + //Performance + + /// Gets or sets whether an encrypted login token should be saved to temp dir after successful login. + public bool CacheToken { get; set; } = true; + /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. + public int MessageCacheSize { get; set; } = 100; + /// + /// Gets or sets whether the permissions cache should be used. + /// This makes operations such as User.GetPermissions(Channel), User.ServerPermissions, Channel.GetUser, and Channel.Members much faster while increasing memory usage. + /// + public bool UsePermissionsCache { get; set; } = true; + /// Gets or sets whether the a copy of a model is generated on an update event to allow you to check which properties changed. + public bool EnablePreUpdateEvents { get; set; } = true; + /// + /// Gets or sets the max number of users a server may have for offline users to be included in the READY packet. Max is 250. + /// Decreasing this may reduce CPU usage while increasing login time and network usage. + /// + public int LargeThreshold { get; set; } = 250; + + //Events + + /// Gets or sets a handler for all log messages. + public EventHandler LogHandler { get; set; } + + public DiscordConfig Build() => new DiscordConfig(this); + } + + public class DiscordConfig + { + public const int MaxMessageSize = 2000; + internal const int RestTimeout = 10000; + internal const int MessageQueueInterval = 100; + internal const int WebSocketQueueInterval = 100; + internal const int ServerBatchCount = 50; + + public const string LibName = "Discord.Net"; + public static string LibVersion => typeof(DiscordConfigBuilder).GetTypeInfo().Assembly.GetName().Version.ToString(3); + public const string LibUrl = "https://github.com/RogueException/Discord.Net"; + + public const string ClientAPIUrl = "https://discordapp.com/api/"; + public const string StatusAPIUrl = "https://srhpyqt94yxb.statuspage.io/api/v2/"; //"https://status.discordapp.com/api/v2/"; + public const string CDNUrl = "https://cdn.discordapp.com/"; + public const string InviteUrl = "https://discord.gg/"; + + public LogSeverity LogLevel { get; } + + public string AppName { get; } + public string AppUrl { get; } + public string AppVersion { get; } + public string UserAgent { get; } + public string CacheDir { get; } + + public int ConnectionTimeout { get; } + public int ReconnectDelay { get; } + public int FailedReconnectDelay { get; } + + public int LargeThreshold { get; } + public int MessageCacheSize { get; } + public bool UsePermissionsCache { get; } + public bool EnablePreUpdateEvents { get; } + + internal DiscordConfig(DiscordConfigBuilder builder) + { + LogLevel = builder.LogLevel; + + AppName = builder.AppName; + AppUrl = builder.AppUrl; + AppVersion = builder.AppVersion; + UserAgent = GetUserAgent(builder); + CacheDir = GetCacheDir(builder); + + ConnectionTimeout = builder.ConnectionTimeout; + ReconnectDelay = builder.ReconnectDelay; + FailedReconnectDelay = builder.FailedReconnectDelay; + + MessageCacheSize = builder.MessageCacheSize; + UsePermissionsCache = builder.UsePermissionsCache; + EnablePreUpdateEvents = builder.EnablePreUpdateEvents; + } + + private static string GetUserAgent(DiscordConfigBuilder builder) + { + StringBuilder sb = new StringBuilder(); + if (!string.IsNullOrEmpty(builder.AppName)) + { + sb.Append(builder.AppName); + if (!string.IsNullOrEmpty(builder.AppVersion)) + { + sb.Append('/'); + sb.Append(builder.AppVersion); + } + if (!string.IsNullOrEmpty(builder.AppUrl)) + { + sb.Append(" ("); + sb.Append(builder.AppUrl); + sb.Append(')'); + } + sb.Append(' '); + } + sb.Append($"DiscordBot ({LibUrl}, v{LibVersion})"); + return sb.ToString(); + } + private static string GetCacheDir(DiscordConfigBuilder builder) + { + if (builder.CacheToken) + return Path.Combine(Path.GetTempPath(), builder.AppName ?? "Discord.Net"); + else + return null; + } + } +} diff --git a/discord.net/src/Discord.Net/DynamicIL.cs b/discord.net/src/Discord.Net/DynamicIL.cs new file mode 100644 index 000000000..bce89424d --- /dev/null +++ b/discord.net/src/Discord.Net/DynamicIL.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; + +namespace Discord +{ + internal static class DynamicIL + { + public static Action CreateCopyMethod() + { + var method = new DynamicMethod("CopyTo", null, new[] { typeof(T), typeof(T) }, typeof(T), true); + var generator = method.GetILGenerator(); + var typeInfo = typeof(T).GetTypeInfo(); + + typeInfo.ForEachField(f => + { + generator.Emit(OpCodes.Ldarg_1); //Stack: TargetRef + generator.Emit(OpCodes.Ldarg_0); //Stack: TargetRef, SourceRef + generator.Emit(OpCodes.Ldfld, f); //Stack: TargetRef, Value + generator.Emit(OpCodes.Stfld, f); //Stack: + }); + + generator.Emit(OpCodes.Ret); + + return method.CreateDelegate(typeof(Action)) as Action; + } + + public static void ForEachField(this TypeInfo typeInfo, Action func) + { + var baseType = typeInfo.BaseType; + if (baseType != null) + baseType.GetTypeInfo().ForEachField(func); + + foreach (var field in typeInfo.DeclaredFields.Where(x => !x.IsStatic)) + func(field); + } + public static void ForEachProperty(this TypeInfo typeInfo, Action func) + { + var baseType = typeInfo.BaseType; + if (baseType != null) + baseType.GetTypeInfo().ForEachProperty(func); + + foreach (var prop in typeInfo.DeclaredProperties.Where(x => + (!x.CanRead || !x.GetMethod.IsStatic) && (!x.CanWrite || !x.SetMethod.IsStatic))) + func(prop); + } + } +} diff --git a/discord.net/src/Discord.Net/ETF/ETFReader.cs b/discord.net/src/Discord.Net/ETF/ETFReader.cs new file mode 100644 index 000000000..600370ca5 --- /dev/null +++ b/discord.net/src/Discord.Net/ETF/ETFReader.cs @@ -0,0 +1,491 @@ +//using Newtonsoft.Json; +//using System; +//using System.Collections.Concurrent; +//using System.Collections.Generic; +//using System.IO; +//using System.Linq; +//using System.Reflection; +//using System.Reflection.Emit; +//using System.Text; + +//namespace Discord.ETF +//{ +// public class ETFReader : IDisposable +// { +// private static readonly ConcurrentDictionary _deserializers = new ConcurrentDictionary(); +// private static readonly Dictionary _readMethods = GetPrimitiveReadMethods(); + +// private readonly Stream _stream; +// private readonly byte[] _buffer; +// private readonly bool _leaveOpen; +// private readonly Encoding _encoding; + +// public ETFReader(Stream stream, bool leaveOpen = false) +// { +// if (stream == null) throw new ArgumentNullException(nameof(stream)); + +// _stream = stream; +// _leaveOpen = leaveOpen; +// _buffer = new byte[11]; +// _encoding = Encoding.UTF8; +// } + +// public bool ReadBool() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT) +// { +// _stream.Read(_buffer, 0, 1); +// switch (_buffer[0]) //Length +// { +// case 4: +// ReadTrue(); +// return true; +// case 5: +// ReadFalse(); +// return false; +// } +// } +// throw new InvalidDataException(); +// } +// private void ReadTrue() +// { +// _stream.Read(_buffer, 0, 4); +// if (_buffer[0] != 't' || _buffer[1] != 'r' || _buffer[2] != 'u' || _buffer[3] != 'e') +// throw new InvalidDataException(); +// } +// private void ReadFalse() +// { +// _stream.Read(_buffer, 0, 5); +// if (_buffer[0] != 'f' || _buffer[1] != 'a' || _buffer[2] != 'l' || _buffer[3] != 's' || _buffer[4] != 'e') +// throw new InvalidDataException(); +// } + +// public int ReadSByte() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return (sbyte)ReadLongInternal(type); +// } +// public uint ReadByte() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return (byte)ReadLongInternal(type); +// } +// public int ReadShort() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return (short)ReadLongInternal(type); +// } +// public uint ReadUShort() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return (ushort)ReadLongInternal(type); +// } +// public int ReadInt() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return (int)ReadLongInternal(type); +// } +// public uint ReadUInt() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return (uint)ReadLongInternal(type); +// } +// public long ReadLong() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return ReadLongInternal(type); +// } +// public ulong ReadULong() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return (ulong)ReadLongInternal(type); +// } +// public long ReadLongInternal(ETFType type) +// { +// switch (type) +// { +// case ETFType.SMALL_INTEGER_EXT: +// _stream.Read(_buffer, 0, 1); +// return _buffer[0]; +// case ETFType.INTEGER_EXT: +// _stream.Read(_buffer, 0, 4); +// return (_buffer[0] << 24) | (_buffer[1] << 16) | (_buffer[2] << 8) | (_buffer[3]); +// case ETFType.SMALL_BIG_EXT: +// _stream.Read(_buffer, 0, 2); +// bool isPositive = _buffer[0] == 0; +// byte count = _buffer[1]; + +// int shiftValue = (count - 1) * 8; +// ulong value = 0; +// _stream.Read(_buffer, 0, count); +// for (int i = 0; i < count; i++, shiftValue -= 8) +// value = value + _buffer[i] << shiftValue; +// if (!isPositive) +// return -(long)value; +// else +// return (long)value; +// } +// throw new InvalidDataException(); +// } + +// public float ReadSingle() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return (float)ReadDoubleInternal(type); +// } +// public double ReadDouble() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// return ReadDoubleInternal(type); +// } +// public double ReadDoubleInternal(ETFType type) +// { +// throw new NotImplementedException(); +// } + +// public bool? ReadNullableBool() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT) +// { +// _stream.Read(_buffer, 0, 1); +// switch (_buffer[0]) //Length +// { +// case 3: +// if (ReadNil()) +// return null; +// break; +// case 4: +// ReadTrue(); +// return true; +// case 5: +// ReadFalse(); +// return false; +// } +// } +// throw new InvalidDataException(); +// } +// public int? ReadNullableSByte() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return (sbyte)ReadLongInternal(type); +// } +// public uint? ReadNullableByte() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return (byte)ReadLongInternal(type); +// } +// public int? ReadNullableShort() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return (short)ReadLongInternal(type); +// } +// public uint? ReadNullableUShort() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return (ushort)ReadLongInternal(type); +// } +// public int? ReadNullableInt() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return (int)ReadLongInternal(type); +// } +// public uint? ReadNullableUInt() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return (uint)ReadLongInternal(type); +// } +// public long? ReadNullableLong() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return ReadLongInternal(type); +// } +// public ulong? ReadNullableULong() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return (ulong)ReadLongInternal(type); +// } +// public float? ReadNullableSingle() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return (float)ReadDoubleInternal(type); +// } +// public double? ReadNullableDouble() +// { +// _stream.Read(_buffer, 0, 1); +// ETFType type = (ETFType)_buffer[0]; +// if (type == ETFType.SMALL_ATOM_EXT && ReadNil()) return null; +// return ReadDoubleInternal(type); +// } + +// public string ReadString() +// { +// throw new NotImplementedException(); +// } +// public byte[] ReadByteArray() +// { +// throw new NotImplementedException(); +// } + +// public T Read() +// where T : new() +// { +// var type = typeof(T); +// var typeInfo = type.GetTypeInfo(); +// var action = _deserializers.GetOrAdd(type, _ => CreateDeserializer(type, typeInfo)) as Func; +// return action(this); +// } +// /*public void Read() +// where T : Nullable +// where U : struct, new() +// { +// }*/ +// public T[] ReadArray() +// { +// throw new NotImplementedException(); +// } +// public IDictionary ReadDictionary() +// { +// throw new NotImplementedException(); +// } +// /*public object Read(object obj) +// { +// throw new NotImplementedException(); +// }*/ + +// private bool ReadNil(bool ignoreLength = false) +// { +// if (!ignoreLength) +// { +// _stream.Read(_buffer, 0, 1); +// byte length = _buffer[0]; +// if (length != 3) return false; +// } + +// _stream.Read(_buffer, 0, 3); +// if (_buffer[0] == 'n' && _buffer[1] == 'i' && _buffer[2] == 'l') +// return true; + +// return false; +// } + +// #region Emit +// private static Func CreateDeserializer(Type type, TypeInfo typeInfo) +// where T : new() +// { +// var method = new DynamicMethod("DeserializeETF", type, new[] { typeof(ETFReader) }, true); +// var generator = method.GetILGenerator(); + +// generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) +// EmitReadValue(generator, type, typeInfo, true); + +// generator.Emit(OpCodes.Ret); +// return method.CreateDelegate(typeof(Func)) as Func; +// } +// private static void EmitReadValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) +// { +// //Convert enum types to their base type +// if (typeInfo.IsEnum) +// { +// type = Enum.GetUnderlyingType(type); +// typeInfo = type.GetTypeInfo(); +// } +// //Primitives/Enums +// if (!typeInfo.IsEnum && IsType(type, typeof(sbyte), typeof(byte), typeof(short), +// typeof(ushort), typeof(int), typeof(uint), typeof(long), +// typeof(ulong), typeof(double), typeof(bool), typeof(string), +// typeof(sbyte?), typeof(byte?), typeof(short?), typeof(ushort?), +// typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), +// typeof(bool?), typeof(float?), typeof(double?) +// /*typeof(object), typeof(DateTime)*/)) +// { +// //No conversion needed +// generator.EmitCall(OpCodes.Call, GetReadMethod(type), null); +// } +// //Dictionaries +// /*else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces +// .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) +// { +// generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); +// } +// //Enumerable +// else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces +// .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) +// { +// generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); +// } +// //Nullable Structs +// else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && +// typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) +// { +// generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); +// } +// //Structs/Classes +// else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) +// { +// if (isTop) +// { +// typeInfo.ForEachField(f => +// { +// string name; +// if (!f.IsPublic || !IsETFProperty(f, out name)) return; + +// generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) +// generator.Emit(OpCodes.Ldstr, name); //ETFReader(this), name +// generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); +// generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) +// generator.Emit(OpCodes.Ldarg_1); //ETFReader(this), obj +// generator.Emit(OpCodes.Ldfld, f); //ETFReader(this), obj.fieldValue +// EmitWriteValue(generator, f.FieldType, f.FieldType.GetTypeInfo(), false); +// }); + +// typeInfo.ForEachProperty(p => +// { +// string name; +// if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; + +// generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) +// generator.Emit(OpCodes.Ldstr, name); //ETFReader(this), name +// generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); +// generator.Emit(OpCodes.Ldarg_0); //ETFReader(this) +// generator.Emit(OpCodes.Ldarg_1); //ETFReader(this), obj +// generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFReader(this), obj.propValue +// EmitWriteValue(generator, p.PropertyType, p.PropertyType.GetTypeInfo(), false); +// }); +// } +// else +// { +// //While we could drill deeper and make a large serializer that also serializes all subclasses, +// //it's more efficient to serialize on a per-type basis via another Write call. +// generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); +// } +// }*/ +// //Unsupported (decimal, char) +// else +// throw new InvalidOperationException($"Deserializing {type.Name} is not supported."); +// } + +// private static bool IsType(Type type, params Type[] types) +// { +// for (int i = 0; i < types.Length; i++) +// { +// if (type == types[i]) +// return true; +// } +// return false; +// } +// private static bool IsETFProperty(FieldInfo f, out string name) +// { +// var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); +// if (attrib != null) +// { +// name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? f.Name; +// return true; +// } +// name = null; +// return false; +// } +// private static bool IsETFProperty(PropertyInfo p, out string name) +// { +// var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); +// if (attrib != null) +// { +// name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? p.Name; +// return true; +// } +// name = null; +// return false; +// } + +// private static MethodInfo GetReadMethod(string name) +// => typeof(ETFReader).GetTypeInfo().GetDeclaredMethods(name).Single(); +// private static MethodInfo GetReadMethod(Type type) +// { +// MethodInfo method; +// if (_readMethods.TryGetValue(type, out method)) +// return method; +// return null; +// } +// private static Dictionary GetPrimitiveReadMethods() +// { +// return new Dictionary +// { +// { typeof(bool), GetReadMethod(nameof(ReadBool)) }, +// { typeof(bool?), GetReadMethod(nameof(ReadNullableBool)) }, +// { typeof(byte), GetReadMethod(nameof(ReadByte)) }, +// { typeof(byte?), GetReadMethod(nameof(ReadNullableByte)) }, +// { typeof(sbyte), GetReadMethod(nameof(ReadSByte)) }, +// { typeof(sbyte?), GetReadMethod(nameof(ReadNullableSByte)) }, +// { typeof(short), GetReadMethod(nameof(ReadShort)) }, +// { typeof(short?), GetReadMethod(nameof(ReadNullableShort)) }, +// { typeof(ushort), GetReadMethod(nameof(ReadUShort)) }, +// { typeof(ushort?), GetReadMethod(nameof(ReadNullableUShort)) }, +// { typeof(int), GetReadMethod(nameof(ReadInt)) }, +// { typeof(int?), GetReadMethod(nameof(ReadNullableInt)) }, +// { typeof(uint), GetReadMethod(nameof(ReadUInt)) }, +// { typeof(uint?), GetReadMethod(nameof(ReadNullableUInt)) }, +// { typeof(long), GetReadMethod(nameof(ReadLong)) }, +// { typeof(long?), GetReadMethod(nameof(ReadNullableLong)) }, +// { typeof(ulong), GetReadMethod(nameof(ReadULong)) }, +// { typeof(ulong?), GetReadMethod(nameof(ReadNullableULong)) }, +// { typeof(float), GetReadMethod(nameof(ReadSingle)) }, +// { typeof(float?), GetReadMethod(nameof(ReadNullableSingle)) }, +// { typeof(double), GetReadMethod(nameof(ReadDouble)) }, +// { typeof(double?), GetReadMethod(nameof(ReadNullableDouble)) }, +// }; +// } +// #endregion + +// #region IDisposable +// private bool _isDisposed = false; + +// protected virtual void Dispose(bool disposing) +// { +// if (!_isDisposed) +// { +// if (disposing) +// { +// if (_leaveOpen) +// _stream.Flush(); +// else +// _stream.Dispose(); +// } +// _isDisposed = true; +// } +// } + +// public void Dispose() => Dispose(true); +// #endregion +// } +//} \ No newline at end of file diff --git a/discord.net/src/Discord.Net/ETF/ETFType.cs b/discord.net/src/Discord.Net/ETF/ETFType.cs new file mode 100644 index 000000000..53499d5fa --- /dev/null +++ b/discord.net/src/Discord.Net/ETF/ETFType.cs @@ -0,0 +1,32 @@ +namespace Discord.ETF +{ + public enum ETFType : byte + { + NEW_FLOAT_EXT = 70, + BIT_BINARY_EXT = 77, + ATOM_CACHE_REF = 82, + SMALL_INTEGER_EXT = 97, + INTEGER_EXT = 98, + FLOAT_EXT = 99, + ATOM_EXT = 100, + REFERENCE_EXT = 101, + PORT_EXT = 102, + PID_EXT = 103, + SMALL_TUPLE_EXT = 104, + LARGE_TUPLE_EXT = 105, + NIL_EXT = 106, + STRING_EXT = 107, + LIST_EXT = 108, + BINARY_EXT = 109, + SMALL_BIG_EXT = 110, + LARGE_BIG_EXT = 111, + NEW_FUN_EXT = 112, + EXPORT_EXT = 113, + NEW_REFERENCE_EXT = 114, + SMALL_ATOM_EXT = 115, + MAP_EXT = 116, + FUN_EXT = 117, + ATOM_UTF8_EXT = 118, + SMALL_ATOM_UTF8_EXT = 119 + } +} diff --git a/discord.net/src/Discord.Net/ETF/ETFWriter.cs b/discord.net/src/Discord.Net/ETF/ETFWriter.cs new file mode 100644 index 000000000..cae365d7f --- /dev/null +++ b/discord.net/src/Discord.Net/ETF/ETFWriter.cs @@ -0,0 +1,482 @@ +//using Newtonsoft.Json; +//using System; +//using System.Collections.Concurrent; +//using System.Collections.Generic; +//using System.IO; +//using System.Linq; +//using System.Reflection; +//using System.Reflection.Emit; +//using System.Text; + +//namespace Discord.ETF +//{ +// public unsafe class ETFWriter : IDisposable +// { +// private static readonly ConcurrentDictionary _serializers = new ConcurrentDictionary(); +// private static readonly ConcurrentDictionary _indirectSerializers = new ConcurrentDictionary(); + +// private static readonly byte[] _nilBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 3, (byte)'n', (byte)'i', (byte)'l' }; +// private static readonly byte[] _falseBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 5, (byte)'f', (byte)'a', (byte)'l', (byte)'s', (byte)'e' }; +// private static readonly byte[] _trueBytes = new byte[] { (byte)ETFType.SMALL_ATOM_EXT, 4, (byte)'t', (byte)'r', (byte)'u', (byte)'e' }; + +// private static readonly MethodInfo _writeTMethod = GetGenericWriteMethod(null); +// private static readonly MethodInfo _writeNullableTMethod = GetGenericWriteMethod(typeof(Nullable<>)); +// private static readonly MethodInfo _writeDictionaryTMethod = GetGenericWriteMethod(typeof(IDictionary<,>)); +// private static readonly MethodInfo _writeEnumerableTMethod = GetGenericWriteMethod(typeof(IEnumerable<>)); +// private static readonly DateTime _epochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + +// private readonly Stream _stream; +// private readonly byte[] _buffer; +// private readonly bool _leaveOpen; +// private readonly Encoding _encoding; + +// public virtual Stream BaseStream +// { +// get +// { +// Flush(); +// return _stream; +// } +// } + +// public ETFWriter(Stream stream, bool leaveOpen = false) +// { +// if (stream == null) throw new ArgumentNullException(nameof(stream)); + +// _stream = stream; +// _leaveOpen = leaveOpen; +// _buffer = new byte[11]; +// _encoding = Encoding.UTF8; +// } + +// public void Write(bool value) +// { +// if (value) +// _stream.Write(_trueBytes, 0, _trueBytes.Length); +// else +// _stream.Write(_falseBytes, 0, _falseBytes.Length); +// } +// public void Write(sbyte value) => Write((long)value); +// public void Write(byte value) => Write((ulong)value); +// public void Write(short value) => Write((long)value); +// public void Write(ushort value) => Write((ulong)value); +// public void Write(int value) => Write((long)value); +// public void Write(uint value) => Write((ulong)value); +// public void Write(long value) +// { +// if (value >= byte.MinValue && value <= byte.MaxValue) +// { +// _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT; +// _buffer[1] = (byte)value; +// _stream.Write(_buffer, 0, 2); +// } +// else if (value >= int.MinValue && value <= int.MaxValue) +// { +// //TODO: Does this encode negatives correctly? +// _buffer[0] = (byte)ETFType.INTEGER_EXT; +// _buffer[1] = (byte)(value >> 24); +// _buffer[2] = (byte)(value >> 16); +// _buffer[3] = (byte)(value >> 8); +// _buffer[4] = (byte)value; +// _stream.Write(_buffer, 0, 5); +// } +// else +// { +// _buffer[0] = (byte)ETFType.SMALL_BIG_EXT; +// if (value < 0) +// { +// _buffer[2] = 1; //Is negative +// value = -value; +// } + +// byte bytes = 0; +// while (value > 0) +// _buffer[3 + bytes++] = (byte)(value >>= 8); +// _buffer[1] = bytes; //Encoded bytes + +// _stream.Write(_buffer, 0, 3 + bytes); +// } +// } +// public void Write(ulong value) +// { +// if (value <= byte.MaxValue) +// { +// _buffer[0] = (byte)ETFType.SMALL_INTEGER_EXT; +// _buffer[1] = (byte)value; +// _stream.Write(_buffer, 0, 2); +// } +// else if (value <= int.MaxValue) +// { +// _buffer[0] = (byte)ETFType.INTEGER_EXT; +// _buffer[1] = (byte)(value >> 24); +// _buffer[2] = (byte)(value >> 16); +// _buffer[3] = (byte)(value >> 8); +// _buffer[4] = (byte)value; +// _stream.Write(_buffer, 0, 5); +// } +// else +// { +// _buffer[0] = (byte)ETFType.SMALL_BIG_EXT; +// _buffer[2] = 0; //Always positive + +// byte bytes = 0; +// while (value > 0) +// _buffer[3 + bytes++] = (byte)(value >>= 8); +// _buffer[1] = bytes; //Encoded bytes + +// _stream.Write(_buffer, 0, 3 + bytes); +// } +// } + +// public void Write(float value) => Write((double)value); +// public void Write(double value) +// { +// ulong value2 = *(ulong*)&value; +// _buffer[0] = (byte)ETFType.NEW_FLOAT_EXT; +// _buffer[1] = (byte)(value2 >> 56); +// _buffer[2] = (byte)(value2 >> 48); +// _buffer[3] = (byte)(value2 >> 40); +// _buffer[4] = (byte)(value2 >> 32); +// _buffer[5] = (byte)(value2 >> 24); +// _buffer[6] = (byte)(value2 >> 16); +// _buffer[7] = (byte)(value2 >> 8); +// _buffer[8] = (byte)value2; +// _stream.Write(_buffer, 0, 9); +// } + +// public void Write(DateTime value) => Write((ulong)((value.Ticks - _epochTime.Ticks) / TimeSpan.TicksPerSecond)); + +// public void Write(bool? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } +// public void Write(sbyte? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } +// public void Write(byte? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } +// public void Write(short? value) { if (value.HasValue) Write((long)value.Value); else WriteNil(); } +// public void Write(ushort? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } +// public void Write(int? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } +// public void Write(uint? value) { if (value.HasValue) Write((ulong)value.Value); else WriteNil(); } +// public void Write(long? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } +// public void Write(ulong? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } +// public void Write(double? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } +// public void Write(float? value) { if (value.HasValue) Write((double)value.Value); else WriteNil(); } +// public void Write(DateTime? value) { if (value.HasValue) Write(value.Value); else WriteNil(); } + +// public void Write(string value) +// { +// if (value != null) +// { +// var bytes = _encoding.GetBytes(value); +// int count = bytes.Length; +// _buffer[0] = (byte)ETFType.BINARY_EXT; +// _buffer[1] = (byte)(count >> 24); +// _buffer[2] = (byte)(count >> 16); +// _buffer[3] = (byte)(count >> 8); +// _buffer[4] = (byte)count; +// _stream.Write(_buffer, 0, 5); +// _stream.Write(bytes, 0, bytes.Length); +// } +// else +// WriteNil(); +// } +// public void Write(byte[] value) +// { +// if (value != null) +// { +// int count = value.Length; +// _buffer[0] = (byte)ETFType.BINARY_EXT; +// _buffer[1] = (byte)(count >> 24); +// _buffer[2] = (byte)(count >> 16); +// _buffer[3] = (byte)(count >> 8); +// _buffer[4] = (byte)count; +// _stream.Write(_buffer, 0, 5); +// _stream.Write(value, 0, value.Length); +// } +// else +// WriteNil(); +// } + +// public void Write(T obj) +// { +// var type = typeof(T); +// var typeInfo = type.GetTypeInfo(); +// var action = _serializers.GetOrAdd(type, _ => CreateSerializer(type, typeInfo, false)) as Action; +// action(this, obj); +// } +// public void Write(T? obj) +// where T : struct +// { +// if (obj != null) +// Write(obj.Value); +// else +// WriteNil(); +// } +// public void Write(IEnumerable obj) +// { +// if (obj != null) +// { +// var array = obj.ToArray(); +// int length = array.Length; +// _buffer[0] = (byte)ETFType.LIST_EXT; +// _buffer[1] = (byte)(length >> 24); +// _buffer[2] = (byte)(length >> 16); +// _buffer[3] = (byte)(length >> 8); +// _buffer[4] = (byte)length; +// _stream.Write(_buffer, 0, 5); + +// for (int i = 0; i < array.Length; i++) +// Write(array[i]); + +// _buffer[0] = (byte)ETFType.NIL_EXT; +// _stream.Write(_buffer, 0, 1); +// } +// else +// WriteNil(); +// } +// public void Write(IDictionary obj) +// { +// if (obj != null) +// { +// int length = obj.Count; +// _buffer[0] = (byte)ETFType.MAP_EXT; +// _buffer[1] = (byte)(length >> 24); +// _buffer[2] = (byte)(length >> 16); +// _buffer[3] = (byte)(length >> 8); +// _buffer[4] = (byte)length; +// _stream.Write(_buffer, 0, 5); + +// foreach (var pair in obj) +// { +// Write(pair.Key); +// Write(pair.Value); +// } +// } +// else +// WriteNil(); +// } +// public void Write(object obj) +// { +// if (obj != null) +// { +// var type = obj.GetType(); +// var typeInfo = type.GetTypeInfo(); +// var action = _indirectSerializers.GetOrAdd(type, _ => CreateSerializer(type, typeInfo, true)) as Action; +// action(this, obj); +// } +// else +// WriteNil(); +// } + +// private void WriteNil() => _stream.Write(_nilBytes, 0, _nilBytes.Length); + +// public virtual void Flush() => _stream.Flush(); +// public virtual long Seek(int offset, SeekOrigin origin) => _stream.Seek(offset, origin); + +// #region Emit +// private static Action CreateSerializer(Type type, TypeInfo typeInfo, bool isDirect) +// { +// var method = new DynamicMethod(isDirect ? "SerializeETF" : "SerializeIndirectETF", +// null, new[] { typeof(ETFWriter), isDirect ? type : typeof(object) }, true); +// var generator = method.GetILGenerator(); + +// generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) +// generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), value +// if (!isDirect) +// { +// if (typeInfo.IsValueType) //Unbox value types +// generator.Emit(OpCodes.Unbox_Any, type); //ETFWriter(this), real_value +// else //Cast reference types +// generator.Emit(OpCodes.Castclass, type); //ETFWriter(this), real_value +// generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(type), null); //Call generic version +// } +// else +// EmitWriteValue(generator, type, typeInfo, true); + +// generator.Emit(OpCodes.Ret); +// return method.CreateDelegate(typeof(Action)) as Action; +// } +// private static void EmitWriteValue(ILGenerator generator, Type type, TypeInfo typeInfo, bool isTop) +// { +// //Convert enum types to their base type +// if (typeInfo.IsEnum) +// { +// type = Enum.GetUnderlyingType(type); +// typeInfo = type.GetTypeInfo(); +// } + +// //Primitives/Enums +// Type targetType = null; +// if (!typeInfo.IsEnum && IsType(type, typeof(long), typeof(ulong), typeof(double), typeof(bool), typeof(string), +// typeof(sbyte?), typeof(byte?), typeof(short?), typeof(ushort?), +// typeof(int?), typeof(uint?), typeof(long?), typeof(ulong?), +// typeof(bool?), typeof(float?), typeof(double?), +// typeof(object), typeof(DateTime))) +// { +// //No conversion needed +// targetType = type; +// } +// else if (IsType(type, typeof(sbyte), typeof(short), typeof(int))) +// { +// //Convert to long +// generator.Emit(OpCodes.Conv_I8); +// targetType = typeof(long); +// } +// else if (IsType(type, typeof(byte), typeof(ushort), typeof(uint))) +// { +// //Convert to ulong +// generator.Emit(OpCodes.Conv_U8); +// targetType = typeof(ulong); +// } +// else if (IsType(type, typeof(float))) +// { +// //Convert to double +// generator.Emit(OpCodes.Conv_R8); +// targetType = typeof(double); +// } +// if (targetType != null) +// generator.EmitCall(OpCodes.Call, GetWriteMethod(targetType), null); + +// //Dictionaries +// else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces +// .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))) +// { +// generator.EmitCall(OpCodes.Call, _writeDictionaryTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); +// } +// //Enumerable +// else if (!typeInfo.IsValueType && typeInfo.ImplementedInterfaces +// .Any(x => x.IsConstructedGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>))) +// { +// generator.EmitCall(OpCodes.Call, _writeEnumerableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); +// } +// //Nullable Structs +// else if (typeInfo.IsGenericType && typeInfo.GetGenericTypeDefinition() == typeof(Nullable<>) && +// typeInfo.GenericTypeParameters[0].GetTypeInfo().IsValueType) +// { +// generator.EmitCall(OpCodes.Call, _writeNullableTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); +// } +// //Structs/Classes +// else if (typeInfo.IsClass || (typeInfo.IsValueType && !typeInfo.IsPrimitive)) +// { +// if (isTop) +// { +// typeInfo.ForEachField(f => +// { +// string name; +// if (!f.IsPublic || !IsETFProperty(f, out name)) return; + +// generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) +// generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name +// generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); +// generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) +// generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj +// generator.Emit(OpCodes.Ldfld, f); //ETFWriter(this), obj.fieldValue +// EmitWriteValue(generator, f.FieldType, f.FieldType.GetTypeInfo(), false); +// }); + +// typeInfo.ForEachProperty(p => +// { +// string name; +// if (!p.CanRead || !p.GetMethod.IsPublic || !IsETFProperty(p, out name)) return; + +// generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) +// generator.Emit(OpCodes.Ldstr, name); //ETFWriter(this), name +// generator.EmitCall(OpCodes.Call, GetWriteMethod(typeof(string)), null); +// generator.Emit(OpCodes.Ldarg_0); //ETFWriter(this) +// generator.Emit(OpCodes.Ldarg_1); //ETFWriter(this), obj +// generator.EmitCall(OpCodes.Callvirt, p.GetMethod, null); //ETFWriter(this), obj.propValue +// EmitWriteValue(generator, p.PropertyType, p.PropertyType.GetTypeInfo(), false); +// }); +// } +// else +// { +// //While we could drill deeper and make a large serializer that also serializes all subclasses, +// //it's more efficient to serialize on a per-type basis via another Write call. +// generator.EmitCall(OpCodes.Call, _writeTMethod.MakeGenericMethod(typeInfo.GenericTypeParameters), null); +// } +// } +// //Unsupported (decimal, char) +// else +// throw new InvalidOperationException($"Serializing {type.Name} is not supported."); +// } + +// private static bool IsType(Type type, params Type[] types) +// { +// for (int i = 0; i < types.Length; i++) +// { +// if (type == types[i]) +// return true; +// } +// return false; +// } +// private static bool IsETFProperty(FieldInfo f, out string name) +// { +// var attrib = f.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); +// if (attrib != null) +// { +// name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? f.Name; +// return true; +// } +// name = null; +// return false; +// } +// private static bool IsETFProperty(PropertyInfo p, out string name) +// { +// var attrib = p.CustomAttributes.Where(x => x.AttributeType == typeof(JsonPropertyAttribute)).FirstOrDefault(); +// if (attrib != null) +// { +// name = attrib.ConstructorArguments.FirstOrDefault().Value as string ?? p.Name; +// return true; +// } +// name = null; +// return false; +// } + +// private static MethodInfo GetWriteMethod(Type paramType) +// { +// return typeof(ETFWriter).GetTypeInfo().GetDeclaredMethods(nameof(Write)) +// .Where(x => x.GetParameters()[0].ParameterType == paramType) +// .Single(); +// } +// private static MethodInfo GetGenericWriteMethod(Type genericType) +// { +// if (genericType == null) +// { +// return typeof(ETFWriter).GetTypeInfo() +// .GetDeclaredMethods(nameof(Write)) +// .Where(x => x.IsGenericMethodDefinition && x.GetParameters()[0].ParameterType == x.GetGenericArguments()[0]) +// .Single(); +// } +// else +// { +// return typeof(ETFWriter).GetTypeInfo() +// .GetDeclaredMethods(nameof(Write)) +// .Where(x => +// { +// if (!x.IsGenericMethodDefinition) return false; +// var p = x.GetParameters()[0].ParameterType.GetTypeInfo(); +// return p.IsGenericType && p.GetGenericTypeDefinition() == genericType; +// }) +// .Single(); +// } +// } +// #endregion + +// #region IDisposable +// private bool _isDisposed = false; + +// protected virtual void Dispose(bool disposing) +// { +// if (!_isDisposed) +// { +// if (disposing) +// { +// if (_leaveOpen) +// _stream.Flush(); +// else +// _stream.Dispose(); +// } +// _isDisposed = true; +// } +// } + +// public void Dispose() => Dispose(true); +// #endregion +// } +//} diff --git a/discord.net/src/Discord.Net/Enums/ChannelType.cs b/discord.net/src/Discord.Net/Enums/ChannelType.cs new file mode 100644 index 000000000..9ed49a701 --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/ChannelType.cs @@ -0,0 +1,37 @@ +using System; + +namespace Discord +{ + public class ChannelType : StringEnum, IEquatable + { + /// A text-only channel. + public static ChannelType Text { get; } = new ChannelType("text"); + /// A voice-only channel. + public static ChannelType Voice { get; } = new ChannelType("voice"); + + private ChannelType(string value) + : base(value) { } + + public static ChannelType FromString(string value) + { + switch (value) + { + case null: + return null; + case "text": + return Text; + case "voice": + return Voice; + default: + return new ChannelType(value); + } + } + + public static implicit operator ChannelType(string value) => FromString(value); + public static bool operator ==(ChannelType a, ChannelType b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false); + public static bool operator !=(ChannelType a, ChannelType b) => !(a == b); + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => (obj as ChannelType)?.Equals(this) ?? false; + public bool Equals(ChannelType type) => type != null && type.Value == Value; + } +} diff --git a/discord.net/src/Discord.Net/Enums/ConnectionState.cs b/discord.net/src/Discord.Net/Enums/ConnectionState.cs new file mode 100644 index 000000000..42c505ccd --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/ConnectionState.cs @@ -0,0 +1,10 @@ +namespace Discord +{ + public enum ConnectionState : byte + { + Disconnected, + Connecting, + Connected, + Disconnecting + } +} diff --git a/discord.net/src/Discord.Net/Enums/GameType.cs b/discord.net/src/Discord.Net/Enums/GameType.cs new file mode 100644 index 000000000..446271b47 --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/GameType.cs @@ -0,0 +1,8 @@ +namespace Discord +{ + public enum GameType : int + { + Default = 0, // "NotStreaming", pretty much + Twitch + } +} diff --git a/discord.net/src/Discord.Net/Enums/ImageType.cs b/discord.net/src/Discord.Net/Enums/ImageType.cs new file mode 100644 index 000000000..738c67a3d --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/ImageType.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public enum ImageType + { + None, + Jpeg, + Png + } +} diff --git a/discord.net/src/Discord.Net/Enums/LogSeverity.cs b/discord.net/src/Discord.Net/Enums/LogSeverity.cs new file mode 100644 index 000000000..c62d8c250 --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/LogSeverity.cs @@ -0,0 +1,11 @@ +namespace Discord +{ + public enum LogSeverity : byte + { + Error = 1, + Warning = 2, + Info = 3, + Verbose = 4, + Debug = 5 + } +} diff --git a/discord.net/src/Discord.Net/Enums/PermValue.cs b/discord.net/src/Discord.Net/Enums/PermValue.cs new file mode 100644 index 000000000..fe048b016 --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/PermValue.cs @@ -0,0 +1,9 @@ +namespace Discord +{ + public enum PermValue + { + Allow, + Deny, + Inherit + } +} diff --git a/discord.net/src/Discord.Net/Enums/PermissionBits.cs b/discord.net/src/Discord.Net/Enums/PermissionBits.cs new file mode 100644 index 000000000..cebb00606 --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/PermissionBits.cs @@ -0,0 +1,35 @@ +namespace Discord +{ + internal enum PermissionBits : byte + { + //General + CreateInstantInvite = 0, + KickMembers = 1, + BanMembers = 2, + Administrator = 3, + ManageChannel = 4, + ManageServer = 5, + + //Text + ReadMessages = 10, + SendMessages = 11, + SendTTSMessages = 12, + ManageMessages = 13, + EmbedLinks = 14, + AttachFiles = 15, + ReadMessageHistory = 16, + MentionEveryone = 17, + + //Voice + Connect = 20, + Speak = 21, + MuteMembers = 22, + DeafenMembers = 23, + MoveMembers = 24, + UseVoiceActivation = 25, + + ChangeNickname = 26, + ManageNicknames = 27, + ManageRolesOrPermissions = 28, + } +} diff --git a/discord.net/src/Discord.Net/Enums/PermissionTarget.cs b/discord.net/src/Discord.Net/Enums/PermissionTarget.cs new file mode 100644 index 000000000..38a70e013 --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/PermissionTarget.cs @@ -0,0 +1,35 @@ +namespace Discord +{ + public class PermissionTarget : StringEnum + { + /// A text-only channel. + public static PermissionTarget Role { get; } = new PermissionTarget("role"); + /// A voice-only channel. + public static PermissionTarget User { get; } = new PermissionTarget("member"); + + private PermissionTarget(string value) + : base(value) { } + + public static PermissionTarget FromString(string value) + { + switch (value) + { + case null: + return null; + case "role": + return Role; + case "member": + return User; + default: + return new PermissionTarget(value); + } + } + + public static implicit operator PermissionTarget(string value) => FromString(value); + public static bool operator ==(PermissionTarget a, PermissionTarget b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false); + public static bool operator !=(PermissionTarget a, PermissionTarget b) => !(a == b); + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => (obj as PermissionTarget)?.Equals(this) ?? false; + public bool Equals(PermissionTarget type) => type != null && type.Value == Value; + } +} diff --git a/discord.net/src/Discord.Net/Enums/Relative.cs b/discord.net/src/Discord.Net/Enums/Relative.cs new file mode 100644 index 000000000..4bd44c5ab --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/Relative.cs @@ -0,0 +1,7 @@ +namespace Discord +{ + public enum Relative + { + Before, After + } +} diff --git a/discord.net/src/Discord.Net/Enums/StringEnum.cs b/discord.net/src/Discord.Net/Enums/StringEnum.cs new file mode 100644 index 000000000..903bdfdba --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/StringEnum.cs @@ -0,0 +1,14 @@ +namespace Discord +{ + public abstract class StringEnum + { + public string Value { get; } + + protected StringEnum(string value) + { + Value = value; + } + + public override string ToString() => Value; + } +} diff --git a/discord.net/src/Discord.Net/Enums/UserStatus.cs b/discord.net/src/Discord.Net/Enums/UserStatus.cs new file mode 100644 index 000000000..80def4234 --- /dev/null +++ b/discord.net/src/Discord.Net/Enums/UserStatus.cs @@ -0,0 +1,40 @@ +namespace Discord +{ + public class UserStatus : StringEnum + { + /// User is currently online and active. + public static UserStatus Online { get; } = new UserStatus("online"); + /// User is currently online but inactive. + public static UserStatus Idle { get; } = new UserStatus("idle"); + /// User is offline. + public static UserStatus Offline { get; } = new UserStatus("offline"); + + private UserStatus(string value) + : base(value) { } + + public static UserStatus FromString(string value) + { + switch (value) + { + case null: + return null; + case "online": + return Online; + case "idle": + return Idle; + case "offline": + return Offline; + default: + return new UserStatus(value); + } + } + + + public static implicit operator UserStatus(string value) => FromString(value); + public static bool operator ==(UserStatus a, UserStatus b) => ((object)a == null && (object)b == null) || (a?.Equals(b) ?? false); + public static bool operator !=(UserStatus a, UserStatus b) => !(a == b); + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object obj) => (obj as UserStatus)?.Equals(this) ?? false; + public bool Equals(UserStatus type) => type != null && type.Value == Value; + } +} diff --git a/discord.net/src/Discord.Net/Events/ChannelEventArgs.cs b/discord.net/src/Discord.Net/Events/ChannelEventArgs.cs new file mode 100644 index 000000000..4aecf34f1 --- /dev/null +++ b/discord.net/src/Discord.Net/Events/ChannelEventArgs.cs @@ -0,0 +1,13 @@ +using System; + +namespace Discord +{ + public class ChannelEventArgs : EventArgs + { + public Channel Channel { get; } + + public Server Server => Channel.Server; + + public ChannelEventArgs(Channel channel) { Channel = channel; } + } +} diff --git a/discord.net/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs b/discord.net/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs new file mode 100644 index 000000000..fa8da98ea --- /dev/null +++ b/discord.net/src/Discord.Net/Events/ChannelUpdatedEventArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace Discord +{ + public class ChannelUpdatedEventArgs : EventArgs + { + public Channel Before { get; } + public Channel After { get; } + + public Server Server => After.Server; + + public ChannelUpdatedEventArgs(Channel before, Channel after) + { + Before = before; + After = after; + } + } +} diff --git a/discord.net/src/Discord.Net/Events/ChannelUserEventArgs.cs b/discord.net/src/Discord.Net/Events/ChannelUserEventArgs.cs new file mode 100644 index 000000000..819c7fcfa --- /dev/null +++ b/discord.net/src/Discord.Net/Events/ChannelUserEventArgs.cs @@ -0,0 +1,14 @@ +namespace Discord +{ + public class ChannelUserEventArgs + { + public Channel Channel { get; } + public User User { get; } + + public ChannelUserEventArgs(Channel channel, User user) + { + Channel = channel; + User = user; + } + } +} diff --git a/discord.net/src/Discord.Net/Events/DisconnectedEventArgs.cs b/discord.net/src/Discord.Net/Events/DisconnectedEventArgs.cs new file mode 100644 index 000000000..87f9ec955 --- /dev/null +++ b/discord.net/src/Discord.Net/Events/DisconnectedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Discord +{ + public class DisconnectedEventArgs : EventArgs + { + public bool WasUnexpected { get; } + public Exception Exception { get; } + + public DisconnectedEventArgs(bool wasUnexpected, Exception ex) + { + WasUnexpected = wasUnexpected; + Exception = ex; + } + } +} diff --git a/discord.net/src/Discord.Net/Events/LogMessageEventArgs.cs b/discord.net/src/Discord.Net/Events/LogMessageEventArgs.cs new file mode 100644 index 000000000..bd1fa5b93 --- /dev/null +++ b/discord.net/src/Discord.Net/Events/LogMessageEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Discord +{ + public class LogMessageEventArgs : EventArgs + { + public LogSeverity Severity { get; } + public string Source { get; } + public string Message { get; } + public Exception Exception { get; } + + public LogMessageEventArgs(LogSeverity severity, string source, string msg, Exception exception) + { + Severity = severity; + Source = source; + Message = msg; + Exception = exception; + } + } +} diff --git a/discord.net/src/Discord.Net/Events/MessageEventArgs.cs b/discord.net/src/Discord.Net/Events/MessageEventArgs.cs new file mode 100644 index 000000000..7edb23347 --- /dev/null +++ b/discord.net/src/Discord.Net/Events/MessageEventArgs.cs @@ -0,0 +1,15 @@ +using System; + +namespace Discord +{ + public class MessageEventArgs : EventArgs + { + public Message Message { get; } + + public User User => Message.User; + public Channel Channel => Message.Channel; + public Server Server => Message.Server; + + public MessageEventArgs(Message msg) { Message = msg; } + } +} diff --git a/discord.net/src/Discord.Net/Events/MessageUpdatedEventArgs.cs b/discord.net/src/Discord.Net/Events/MessageUpdatedEventArgs.cs new file mode 100644 index 000000000..849f234e1 --- /dev/null +++ b/discord.net/src/Discord.Net/Events/MessageUpdatedEventArgs.cs @@ -0,0 +1,20 @@ +using System; + +namespace Discord +{ + public class MessageUpdatedEventArgs : EventArgs + { + public Message Before { get; } + public Message After { get; } + + public User User => After.User; + public Channel Channel => After.Channel; + public Server Server => After.Server; + + public MessageUpdatedEventArgs(Message before, Message after) + { + Before = before; + After = after; + } + } +} diff --git a/discord.net/src/Discord.Net/Events/ProfileUpdatedEventArgs.cs b/discord.net/src/Discord.Net/Events/ProfileUpdatedEventArgs.cs new file mode 100644 index 000000000..2365908e8 --- /dev/null +++ b/discord.net/src/Discord.Net/Events/ProfileUpdatedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Discord +{ + public class ProfileUpdatedEventArgs : EventArgs + { + public Profile Before { get; } + public Profile After { get; } + + public ProfileUpdatedEventArgs(Profile before, Profile after) + { + Before = before; + After = after; + } + } +} diff --git a/discord.net/src/Discord.Net/Events/RoleEventArgs.cs b/discord.net/src/Discord.Net/Events/RoleEventArgs.cs new file mode 100644 index 000000000..13eb0f7f4 --- /dev/null +++ b/discord.net/src/Discord.Net/Events/RoleEventArgs.cs @@ -0,0 +1,13 @@ +using System; + +namespace Discord +{ + public class RoleEventArgs : EventArgs + { + public Role Role { get; } + + public Server Server => Role.Server; + + public RoleEventArgs(Role role) { Role = role; } + } +} diff --git a/discord.net/src/Discord.Net/Events/RoleUpdatedEventArgs.cs b/discord.net/src/Discord.Net/Events/RoleUpdatedEventArgs.cs new file mode 100644 index 000000000..26151c98b --- /dev/null +++ b/discord.net/src/Discord.Net/Events/RoleUpdatedEventArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace Discord +{ + public class RoleUpdatedEventArgs : EventArgs + { + public Role Before { get; } + public Role After { get; } + + public Server Server => After.Server; + + public RoleUpdatedEventArgs(Role before, Role after) + { + Before = before; + After = after; + } + } +} diff --git a/discord.net/src/Discord.Net/Events/ServerEventArgs.cs b/discord.net/src/Discord.Net/Events/ServerEventArgs.cs new file mode 100644 index 000000000..e9e564e1b --- /dev/null +++ b/discord.net/src/Discord.Net/Events/ServerEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord +{ + public class ServerEventArgs : EventArgs + { + public Server Server { get; } + + public ServerEventArgs(Server server) { Server = server; } + } +} diff --git a/discord.net/src/Discord.Net/Events/ServerUpdatedEventArgs.cs b/discord.net/src/Discord.Net/Events/ServerUpdatedEventArgs.cs new file mode 100644 index 000000000..8532f72dc --- /dev/null +++ b/discord.net/src/Discord.Net/Events/ServerUpdatedEventArgs.cs @@ -0,0 +1,16 @@ +using System; + +namespace Discord +{ + public class ServerUpdatedEventArgs : EventArgs + { + public Server Before { get; } + public Server After { get; } + + public ServerUpdatedEventArgs(Server before, Server after) + { + Before = before; + After = after; + } + } +} diff --git a/discord.net/src/Discord.Net/Events/UserEventArgs.cs b/discord.net/src/Discord.Net/Events/UserEventArgs.cs new file mode 100644 index 000000000..bf7dd2cac --- /dev/null +++ b/discord.net/src/Discord.Net/Events/UserEventArgs.cs @@ -0,0 +1,12 @@ +using System; +namespace Discord +{ + public class UserEventArgs : EventArgs + { + public User User { get; } + + public Server Server => User.Server; + + public UserEventArgs(User user) { User = user; } + } +} diff --git a/discord.net/src/Discord.Net/Events/UserUpdatedEventArgs.cs b/discord.net/src/Discord.Net/Events/UserUpdatedEventArgs.cs new file mode 100644 index 000000000..89e8cce0c --- /dev/null +++ b/discord.net/src/Discord.Net/Events/UserUpdatedEventArgs.cs @@ -0,0 +1,17 @@ +using System; +namespace Discord +{ + public class UserUpdatedEventArgs : EventArgs + { + public User Before { get; } + public User After { get; } + + public Server Server => After.Server; + + public UserUpdatedEventArgs(User before, User after) + { + Before = before; + After = after; + } + } +} diff --git a/discord.net/src/Discord.Net/Format.cs b/discord.net/src/Discord.Net/Format.cs new file mode 100644 index 000000000..0ff7e0a4f --- /dev/null +++ b/discord.net/src/Discord.Net/Format.cs @@ -0,0 +1,110 @@ +using System.Text; + +namespace Discord +{ + public static class Format + { + private static readonly string[] _patterns; + private static readonly StringBuilder _builder; + + static Format() + { + _patterns = new string[] { "__", "_", "**", "*", "~~", "```", "`"}; + _builder = new StringBuilder(DiscordConfig.MaxMessageSize); + } + + /// Removes all special formatting characters from the provided text. + public static string Escape(string text) + { + lock (_builder) + { + _builder.Clear(); + + //Escape all backslashes + for (int i = 0; i < text.Length; i++) + { + _builder.Append(text[i]); + if (text[i] == '\\') + _builder.Append('\\'); + } + + EscapeSubstring(0, _builder.Length); + + return _builder.ToString(); + } + } + private static int EscapeSubstring(int start, int end) + { + int totalAddedChars = 0; + for (int i = start; i < end + totalAddedChars; i++) + { + for (int p = 0; p < _patterns.Length; p++) + { + string pattern = _patterns[p]; + if (i + pattern.Length * 2 > _builder.Length) + continue; + int s = FindPattern(pattern, i, i + 1); + if (s == -1) continue; + int e = FindPattern(pattern, i + 1, end + totalAddedChars); + if (e == -1) continue; + + if (e - s - pattern.Length > 0) + { + //By going right to left, we dont need to adjust any offsets + for (int k = pattern.Length - 1; k >= 0; k--) + _builder.Insert(e + k, '\\'); + for (int k = pattern.Length - 1; k >= 0; k--) + _builder.Insert(s + k, '\\'); + int addedChars = pattern.Length * 2; + addedChars += EscapeSubstring(s + pattern.Length * 2, e + pattern.Length); + i = e + addedChars + pattern.Length - 1; + totalAddedChars += addedChars; + break; + } + } + } + return totalAddedChars; + } + private static int FindPattern(string pattern, int start, int end) + { + for (int j = start; j < end; j++) + { + if (_builder[j] == '\\') + { + j++; + continue; + } + for (int k = 0; k < pattern.Length; k++) + { + if (_builder[j + k] != pattern[k]) + goto nextpos; + } + return j; + nextpos:; + } + return -1; + } + + /// Returns a markdown-formatted string with bold formatting, optionally escaping the contents. + public static string Bold(string text, bool escape = true) + => escape ? $"**{Escape(text)}**" : $"**{text}**"; + /// Returns a markdown-formatted string with italics formatting, optionally escaping the contents. + public static string Italics(string text, bool escape = true) + => escape ? $"*{Escape(text)}*" : $"*{text}*"; + /// Returns a markdown-formatted string with underline formatting, optionally escaping the contents. + public static string Underline(string text, bool escape = true) + => escape ? $"__{Escape(text)}__" : $"__{text}__"; + /// Returns a markdown-formatted string with strikeout formatting, optionally escaping the contents. + public static string Strikeout(string text, bool escape = true) + => escape ? $"~~{Escape(text)}~~" : $"~~{text}~~"; + + /// Returns a markdown-formatted string with strikeout formatting, optionally escaping the contents. + public static string Code(string text, string language = null) + { + if (language != null || text.Contains("\n")) + return $"```{language ?? ""}\n{text}\n```"; + else + return $"`{text}`"; + } + } +} diff --git a/discord.net/src/Discord.Net/IMentionable.cs b/discord.net/src/Discord.Net/IMentionable.cs new file mode 100644 index 000000000..0a4bf439c --- /dev/null +++ b/discord.net/src/Discord.Net/IMentionable.cs @@ -0,0 +1,7 @@ +namespace Discord +{ + public interface IMentionable + { + string Mention { get; } + } +} diff --git a/discord.net/src/Discord.Net/IService.cs b/discord.net/src/Discord.Net/IService.cs new file mode 100644 index 000000000..15f79b0c4 --- /dev/null +++ b/discord.net/src/Discord.Net/IService.cs @@ -0,0 +1,7 @@ +namespace Discord +{ + public interface IService + { + void Install(DiscordClient client); + } +} diff --git a/discord.net/src/Discord.Net/InternalExtensions.cs b/discord.net/src/Discord.Net/InternalExtensions.cs new file mode 100644 index 000000000..9b9a2366d --- /dev/null +++ b/discord.net/src/Discord.Net/InternalExtensions.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Discord +{ + internal static class InternalExtensions + { + internal static readonly IFormatProvider _format = CultureInfo.InvariantCulture; + + public static ulong ToId(this string value) => ulong.Parse(value, NumberStyles.None, _format); + public static ulong? ToNullableId(this string value) => value == null ? (ulong?)null : ulong.Parse(value, NumberStyles.None, _format); + public static bool TryToId(this string value, out ulong result) => ulong.TryParse(value, NumberStyles.None, _format, out result); + + public static string ToIdString(this ulong value) => value.ToString(_format); + public static string ToIdString(this ulong? value) => value?.ToString(_format); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasBit(this uint rawValue, byte bit) => ((rawValue >> bit) & 1U) == 1; + + public static bool TryGetOrAdd(this ConcurrentDictionary d, + TKey key, Func factory, out TValue result) + { + bool created = false; + TValue newValue = default(TValue); + while (true) + { + if (d.TryGetValue(key, out result)) + return false; + if (!created) + { + newValue = factory(key); + created = true; + } + if (d.TryAdd(key, newValue)) + { + result = newValue; + return true; + } + } + } + public static bool TryGetOrAdd(this ConcurrentDictionary d, + TKey key, TValue value, out TValue result) + { + while (true) + { + if (d.TryGetValue(key, out result)) + return false; + if (d.TryAdd(key, value)) + { + result = value; + return true; + } + } + } + + public static IEnumerable Find(this IEnumerable channels, string name, ChannelType type = null, bool exactMatch = false) + { + //Search by name + var query = channels + .Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); + + if (!exactMatch) + { + if (name.Length >= 2 && name[0] == '<' && name[1] == '#' && name[name.Length - 1] == '>') //Search by raw mention + { + ulong id; + if (name.Substring(2, name.Length - 3).TryToId(out id)) + { + var channel = channels.Where(x => x.Id == id).FirstOrDefault(); + if (channel != null) + query = query.Concat(new Channel[] { channel }); + } + } + if (name.Length >= 1 && name[0] == '#' && (type == null || type == ChannelType.Text)) //Search by clean mention + { + string name2 = name.Substring(1); + query = query.Concat(channels + .Where(x => x.Type == ChannelType.Text && string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase))); + } + } + + if (type != null) + query = query.Where(x => x.Type == type); + return query; + } + + public static IEnumerable Find(this IEnumerable users, + string name, ushort? discriminator = null, bool exactMatch = false) + { + //Search by name + var query = users + .Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); + + if (!exactMatch) + { + if (name.Length >= 3 && name[0] == '<' && name[1] == '@' && name[2] == '!' && name[name.Length - 1] == '>') //Search by nickname'd mention + { + ulong id; + if (name.Substring(3, name.Length - 4).TryToId(out id)) + { + var user = users.Where(x => x.Id == id).FirstOrDefault(); + if (user != null) + query = query.Concat(new User[] { user }); + } + } + if (name.Length >= 2 && name[0] == '<' && name[1] == '@' && name[name.Length - 1] == '>') //Search by raw mention + { + ulong id; + if (name.Substring(2, name.Length - 3).TryToId(out id)) + { + var user = users.Where(x => x.Id == id).FirstOrDefault(); + if (user != null) + query = query.Concat(new User[] { user }); + } + } + if (name.Length >= 1 && name[0] == '@') //Search by clean mention + { + string name2 = name.Substring(1); + query = query.Concat(users + .Where(x => string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase))); + } + } + + if (discriminator != null) + query = query.Where(x => x.Discriminator == discriminator.Value); + return query; + } + + public static IEnumerable Find(this IEnumerable roles, string name, bool exactMatch = false) + { + // if (name.StartsWith("@")) + // { + // string name2 = name.Substring(1); + // return _roles.Where(x => x.Server.Id == server.Id && + // string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase) || + // string.Equals(x.Name, name2, StringComparison.OrdinalIgnoreCase)); + // } + // else + return roles.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); + } + + public static IEnumerable Find(this IEnumerable servers, string name, bool exactMatch = false) + => servers.Where(x => string.Equals(x.Name, name, exactMatch ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)); + + public static string Base64(this Stream stream, ImageType type, string existingId) + { + if (type == ImageType.None) + return null; + else if (stream != null) + { + byte[] bytes = new byte[stream.Length - stream.Position]; + stream.Read(bytes, 0, bytes.Length); + + string base64 = Convert.ToBase64String(bytes); + string imageType = type == ImageType.Jpeg ? "image/jpeg;base64" : "image/png;base64"; + return $"data:{imageType},{base64}"; + } + return existingId; + } + } +} diff --git a/discord.net/src/Discord.Net/Legacy.cs b/discord.net/src/Discord.Net/Legacy.cs new file mode 100644 index 000000000..be4cea7c0 --- /dev/null +++ b/discord.net/src/Discord.Net/Legacy.cs @@ -0,0 +1,377 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; + +namespace Discord.Legacy +{ + public static class Mention + { + /// Returns the string used to create a user mention. + [Obsolete("Use User.Mention instead")] + public static string User(User user) + => user.Mention; + /// Returns the string used to create a channel mention. + [Obsolete("Use Channel.Mention instead")] + public static string Channel(Channel channel) + => channel.Mention; + /// Returns the string used to create a mention to everyone in a channel. + [Obsolete("Use Server.EveryoneRole.Mention instead")] + public static string Everyone() + => $"@everyone"; + } + + public static class LegacyExtensions + { + [Obsolete("Use DiscordClient.ExecuteAndWait")] + public static void Run(this DiscordClient client, Func asyncAction) + { + client.ExecuteAndWait(asyncAction); + } + [Obsolete("Use DiscordClient.Wait")] + public static void Run(this DiscordClient client) + { + client.Wait(); + } + + [Obsolete("Use Server.FindChannels")] + public static IEnumerable FindChannels(this DiscordClient client, Server server, string name, ChannelType type = null, bool exactMatch = false) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.FindChannels(name, type, exactMatch); + } + + [Obsolete("Use Server.CreateChannel")] + public static Task CreateChannel(this DiscordClient client, Server server, string name, ChannelType type) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.CreateChannel(name, type); + } + [Obsolete("Use User.CreateChannel")] + public static Task CreatePMChannel(this DiscordClient client, User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.CreatePMChannel(); + } + [Obsolete("Use Channel.Edit")] + public static Task EditChannel(this DiscordClient client, Channel channel, string name = null, string topic = null, int? position = null) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.Edit(name, topic, position); + } + [Obsolete("Use Channel.Delete")] + public static Task DeleteChannel(this DiscordClient client, Channel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.Delete(); + } + + [Obsolete("Use Server.ReorderChannels")] + public static Task ReorderChannels(this DiscordClient client, Server server, IEnumerable channels, Channel after = null) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.ReorderChannels(channels, after); + } + + [Obsolete("Use Server.GetInvites")] + public static Task> GetInvites(this DiscordClient client, Server server) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.GetInvites(); + } + + [Obsolete("Use Server.CreateInvite")] + public static Task CreateInvite(this DiscordClient client, Server server, int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.CreateInvite(maxAge, maxUses, tempMembership, withXkcd); + } + [Obsolete("Use Channel.CreateInvite")] + public static Task CreateInvite(this DiscordClient client, Channel channel, int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.CreateInvite(maxAge, maxUses, tempMembership, withXkcd); + } + + [Obsolete("Use Invite.Delete")] + public static Task DeleteInvite(this DiscordClient client, Invite invite) + { + if (invite == null) throw new ArgumentNullException(nameof(invite)); + return invite.Delete(); + } + [Obsolete("Use Invite.Accept")] + public static Task AcceptInvite(this DiscordClient client, Invite invite) + { + if (invite == null) throw new ArgumentNullException(nameof(invite)); + return invite.Accept(); + } + + [Obsolete("Use Channel.SendMessage")] + public static Task SendMessage(this DiscordClient client, Channel channel, string text) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.SendMessage(text); + } + [Obsolete("Use Channel.SendTTSMessage")] + public static Task SendTTSMessage(this DiscordClient client, Channel channel, string text) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.SendTTSMessage(text); + } + [Obsolete("Use Channel.SendFile")] + public static Task SendFile(this DiscordClient client, Channel channel, string filePath) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.SendFile(filePath); + } + [Obsolete("Use Channel.SendFile")] + public static Task SendFile(this DiscordClient client, Channel channel, string filename, Stream stream) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.SendFile(filename, stream); + } + [Obsolete("Use User.SendMessage")] + public static Task SendMessage(this DiscordClient client, User user, string text) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.SendMessage(text); + } + [Obsolete("Use User.SendFile")] + public static Task SendFile(this DiscordClient client, User user, string filePath) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.SendFile(filePath); + } + [Obsolete("Use User.SendFile")] + public static Task SendFile(this DiscordClient client, User user, string filename, Stream stream) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.SendFile(filename, stream); + } + + [Obsolete("Use Message.Edit")] + public static Task EditMessage(this DiscordClient client, Message message, string text) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + return message.Edit(text); + } + + [Obsolete("Use Message.Delete")] + public static Task DeleteMessage(this DiscordClient client, Message message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + return message.Delete(); + } + [Obsolete("Use Message.Delete")] + public static async Task DeleteMessages(this DiscordClient client, IEnumerable messages) + { + if (messages == null) throw new ArgumentNullException(nameof(messages)); + + foreach (var message in messages) + await message.Delete().ConfigureAwait(false); + } + + [Obsolete("Use Channel.DownloadMessages")] + public static Task DownloadMessages(this DiscordClient client, Channel channel, int limit = 100, ulong? relativeMessageId = null, Relative relativeDir = Relative.Before, bool useCache = true) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.DownloadMessages(limit, relativeMessageId, relativeDir, useCache); + } + + [Obsolete("Use Server.GetUser")] + public static User GetUser(this DiscordClient client, Server server, ulong userId) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.GetUser(userId); + } + [Obsolete("Use Server.GetUser")] + public static User GetUser(this DiscordClient client, Server server, string username, ushort discriminator) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.GetUser(username, discriminator); + } + + [Obsolete("Use Server.FindUsers")] + public static IEnumerable FindUsers(this DiscordClient client, Server server, string name, bool exactMatch = false) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.FindUsers(name, exactMatch); + } + [Obsolete("Use Channel.FindUsers")] + public static IEnumerable FindUsers(this DiscordClient client, Channel channel, string name, bool exactMatch = false) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.FindUsers(name, exactMatch); + } + + [Obsolete("Use User.Edit")] + public static Task EditUser(this DiscordClient client, User user, bool? isMuted = null, bool? isDeafened = null, Channel voiceChannel = null, IEnumerable roles = null) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.Edit(isMuted, isDeafened, voiceChannel, roles); + } + + [Obsolete("Use User.Kick")] + public static Task KickUser(this DiscordClient client, User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return user.Kick(); + } + [Obsolete("Use Server.Ban")] + public static Task BanUser(this DiscordClient client, User user, int pruneDays = 0) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + var server = user.Server; + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.Ban(user, pruneDays); + } + [Obsolete("Use Server.Unban")] + public static Task UnbanUser(this DiscordClient client, User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + var server = user.Server; + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.Unban(user); + } + [Obsolete("Use Server.Unban")] + public static Task UnbanUser(this DiscordClient client, Server server, ulong userId) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.Unban(userId); + } + + [Obsolete("Use Server.PruneUsers")] + public static Task PruneUsers(this DiscordClient client, Server server, int days = 30, bool simulate = false) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.PruneUsers(days, simulate); + } + + [Obsolete("Use DiscordClient.CurrentUser.Edit")] + public static Task EditProfile(this DiscordClient client, string currentPassword = "", + string username = null, string email = null, string password = null, + Stream avatar = null, ImageType avatarType = ImageType.Png) + => client.CurrentUser.Edit(currentPassword, username, email, password, avatar, avatarType); + + [Obsolete("Use Server.GetRole")] + public static Role GetRole(this DiscordClient client, Server server, ulong id) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.GetRole(id); + } + [Obsolete("Use Server.FindRoles")] + public static IEnumerable FindRoles(this DiscordClient client, Server server, string name) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.FindRoles(name); + } + + [Obsolete("Use Server.CreateRole")] + public static Task CreateRole(this DiscordClient client, Server server, string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.CreateRole(name, permissions, color); + } + [Obsolete("Use Role.Edit")] + public static Task EditRole(this DiscordClient client, Role role, string name = null, ServerPermissions? permissions = null, Color color = null, bool? isHoisted = null, int? position = null) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + return role.Edit(name, permissions, color, isHoisted, position); + } + + [Obsolete("Use Role.Delete")] + public static Task DeleteRole(this DiscordClient client, Role role) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + return role.Delete(); + } + + [Obsolete("Use Server.ReorderRoles")] + public static Task ReorderRoles(this DiscordClient client, Server server, IEnumerable roles, Role after = null) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.ReorderRoles(roles, after); + } + + [Obsolete("Use Server.Edit")] + public static Task EditServer(this DiscordClient client, Server server, string name = null, string region = null, Stream icon = null, ImageType iconType = ImageType.Png) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.Edit(name, region, icon, iconType); + } + + [Obsolete("Use Server.Leave")] + public static Task LeaveServer(this DiscordClient client, Server server) + { + if (server == null) throw new ArgumentNullException(nameof(server)); + return server.Leave(); + } + + [Obsolete("Use DiscordClient.Regions")] + public static IEnumerable GetVoiceRegions(this DiscordClient client) + => client.Regions; + + [Obsolete("Use Channel.GetPermissionRule")] + public static ChannelPermissionOverrides GetChannelPermissions(this DiscordClient client, Channel channel, User user) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.GetPermissionsRule(user); + } + [Obsolete("Use Channel.GetPermissionRule")] + public static ChannelPermissionOverrides GetChannelPermissions(this DiscordClient client, Channel channel, Role role) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.GetPermissionsRule(role); + } + [Obsolete("Use Channel.AddPermissionRule(DualChannelPermissions)", true)] + public static Task SetChannelPermissions(this DiscordClient client, Channel channel, User user, ChannelPermissions allow, ChannelPermissions deny) + { + throw new InvalidOperationException(); + } + [Obsolete("Use Channel.AddPermissionRule")] + public static Task SetChannelPermissions(this DiscordClient client, Channel channel, User user, ChannelPermissionOverrides permissions) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.AddPermissionsRule(user, permissions); + } + [Obsolete("Use Channel.AddPermissionRule(DualChannelPermissions)")] + public static Task SetChannelPermissions(this DiscordClient client, Channel channel, Role role, ChannelPermissions allow, ChannelPermissions deny) + { + throw new InvalidOperationException(); + } + [Obsolete("Use Channel.AddPermissionRule")] + public static Task SetChannelPermissions(this DiscordClient client, Channel channel, Role role, ChannelPermissionOverrides permissions) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.AddPermissionsRule(role, permissions); + } + [Obsolete("Use Channel.RemovePermissionRule")] + public static Task RemoveChannelPermissions(this DiscordClient client, Channel channel, User user) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.RemovePermissionsRule(user); + } + [Obsolete("Use Channel.RemovePermissionRule")] + public static Task RemoveChannelPermissions(this DiscordClient client, Channel channel, Role role) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.RemovePermissionsRule(role); + } + + [Obsolete("Removed", true)] + public static Task AckMessage(this DiscordClient client, Message message) + { + throw new InvalidOperationException(); + } + [Obsolete("Use Channel.ImportMessages", true)] + public static IEnumerable ImportMessages(Channel channel, string json) + { + throw new InvalidOperationException(); + } + [Obsolete("Use Channel.ExportMessages", true)] + public static string ExportMessages(Channel channel) + { + throw new InvalidOperationException(); + } + } +} diff --git a/discord.net/src/Discord.Net/Logging/ILogger.cs b/discord.net/src/Discord.Net/Logging/ILogger.cs new file mode 100644 index 000000000..3282c80fa --- /dev/null +++ b/discord.net/src/Discord.Net/Logging/ILogger.cs @@ -0,0 +1,44 @@ +using System; + +namespace Discord.Logging +{ + public interface ILogger + { + LogSeverity Level { get; } + + void Log(LogSeverity severity, string message, Exception exception = null); +#if NETSTANDARD1_3 + void Log(LogSeverity severity, FormattableString message, Exception exception = null); +#endif + + void Error(string message, Exception exception = null); +#if NETSTANDARD1_3 + void Error(FormattableString message, Exception exception = null); +#endif + void Error(Exception exception); + + void Warning(string message, Exception exception = null); +#if NETSTANDARD1_3 + void Warning(FormattableString message, Exception exception = null); +#endif + void Warning(Exception exception); + + void Info(string message, Exception exception = null); +#if NETSTANDARD1_3 + void Info(FormattableString message, Exception exception = null); +#endif + void Info(Exception exception); + + void Verbose(string message, Exception exception = null); +#if NETSTANDARD1_3 + void Verbose(FormattableString message, Exception exception = null); +#endif + void Verbose(Exception exception); + + void Debug(string message, Exception exception = null); +#if NETSTANDARD1_3 + void Debug(FormattableString message, Exception exception = null); +#endif + void Debug(Exception exception); + } +} diff --git a/discord.net/src/Discord.Net/Logging/LogManager.cs b/discord.net/src/Discord.Net/Logging/LogManager.cs new file mode 100644 index 000000000..3688ca29b --- /dev/null +++ b/discord.net/src/Discord.Net/Logging/LogManager.cs @@ -0,0 +1,75 @@ +using System; + +namespace Discord.Logging +{ + public class LogManager + { + private readonly DiscordClient _client; + + public LogSeverity Level { get; } + + public event EventHandler Message = delegate { }; + + internal LogManager(DiscordClient client) + { + _client = client; + Level = client.Config.LogLevel; + } + + public void Log(LogSeverity severity, string source, string message, Exception exception = null) + { + if (severity <= Level) + { + try { Message(this, new LogMessageEventArgs(severity, source, message, exception)); } + catch { } //We dont want to log on log errors + } + } + +#if NETSTANDARD1_3 + public void Log(LogSeverity severity, string source, FormattableString message, Exception exception = null) + { + if (severity <= Level) + { + try { Message(this, new LogMessageEventArgs(severity, source, message.ToString(), exception)); } + catch { } //We dont want to log on log errors + } + } +#endif + + public void Error(string source, string message, Exception ex = null) + => Log(LogSeverity.Error, source, message, ex); + public void Error(string source, Exception ex) + => Log(LogSeverity.Error, source, (string)null, ex); + public void Warning(string source, string message, Exception ex = null) + => Log(LogSeverity.Warning, source, message, ex); + public void Warning(string source, Exception ex) + => Log(LogSeverity.Warning, source, (string)null, ex); + public void Info(string source, string message, Exception ex = null) + => Log(LogSeverity.Info, source, message, ex); + public void Info(string source, Exception ex) + => Log(LogSeverity.Info, source, (string)null, ex); + public void Verbose(string source, string message, Exception ex = null) + => Log(LogSeverity.Verbose, source, message, ex); + public void Verbose(string source, Exception ex) + => Log(LogSeverity.Verbose, source, (string)null, ex); + public void Debug(string source, string message, Exception ex = null) + => Log(LogSeverity.Debug, source, message, ex); + public void Debug(string source, Exception ex) + => Log(LogSeverity.Debug, source, (string)null, ex); + +#if NETSTANDARD1_3 + public void Error(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Error, source, message, ex); + public void Warning(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Warning, source, message, ex); + public void Info(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Info, source, message, ex); + public void Verbose(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Verbose, source, message, ex); + public void Debug(string source, FormattableString message, Exception ex = null) + => Log(LogSeverity.Debug, source, message, ex); +#endif + + public Logger CreateLogger(string name) => new Logger(this, name); + } +} diff --git a/discord.net/src/Discord.Net/Logging/Logger.cs b/discord.net/src/Discord.Net/Logging/Logger.cs new file mode 100644 index 000000000..5d5b041f9 --- /dev/null +++ b/discord.net/src/Discord.Net/Logging/Logger.cs @@ -0,0 +1,56 @@ +using System; + +namespace Discord.Logging +{ + public class Logger : ILogger + { + private readonly LogManager _manager; + + public string Name { get; } + public LogSeverity Level => _manager.Level; + + internal Logger(LogManager manager, string name) + { + _manager = manager; + Name = name; + } + + public void Log(LogSeverity severity, string message, Exception exception = null) + => _manager.Log(severity, Name, message, exception); + public void Error(string message, Exception exception = null) + => _manager.Error(Name, message, exception); + public void Error(Exception exception) + => _manager.Error(Name, exception); + public void Warning(string message, Exception exception = null) + => _manager.Warning(Name, message, exception); + public void Warning(Exception exception) + => _manager.Warning(Name, exception); + public void Info(string message, Exception exception = null) + => _manager.Info(Name, message, exception); + public void Info(Exception exception) + => _manager.Info(Name, exception); + public void Verbose(string message, Exception exception = null) + => _manager.Verbose(Name, message, exception); + public void Verbose(Exception exception) + => _manager.Verbose(Name, exception); + public void Debug(string message, Exception exception = null) + => _manager.Debug(Name, message, exception); + public void Debug(Exception exception) + => _manager.Debug(Name, exception); + +#if NETSTANDARD1_3 + public void Log(LogSeverity severity, FormattableString message, Exception exception = null) + => _manager.Log(severity, Name, message, exception); + public void Error(FormattableString message, Exception exception = null) + => _manager.Error(Name, message, exception); + public void Warning(FormattableString message, Exception exception = null) + => _manager.Warning(Name, message, exception); + public void Info(FormattableString message, Exception exception = null) + => _manager.Info(Name, message, exception); + public void Verbose(FormattableString message, Exception exception = null) + => _manager.Verbose(Name, message, exception); + public void Debug(FormattableString message, Exception exception = null) + => _manager.Debug(Name, message, exception); +#endif + } +} diff --git a/discord.net/src/Discord.Net/MessageQueue.cs b/discord.net/src/Discord.Net/MessageQueue.cs new file mode 100644 index 000000000..99cfd6c3f --- /dev/null +++ b/discord.net/src/Discord.Net/MessageQueue.cs @@ -0,0 +1,264 @@ +using Discord.API.Client.Rest; +using Discord.Logging; +using Discord.Net.Rest; +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net +{ + /// Manages an outgoing message queue for DiscordClient. + public class MessageQueue + { + private struct MessageEdit + { + public readonly Message Message; + public readonly string NewText; + + public MessageEdit(Message message, string newText) + { + Message = message; + NewText = newText; + } + } + + private const int WarningStart = 30; + + private readonly Random _nonceRand; + private readonly RestClient _rest; + private readonly Logger _logger; + private readonly ConcurrentQueue _pendingSends; + private readonly ConcurrentQueue _pendingEdits; + private readonly ConcurrentQueue _pendingDeletes; + private readonly ConcurrentDictionary _pendingSendsByNonce; + private int _count, _nextWarning; + + /// Gets the current number of queued actions. + public int Count => _count; + /// Gets the current number of queued sends. + public int SendCount => _pendingSends.Count; + /// Gets the current number of queued edits. + public int EditCount => _pendingEdits.Count; + /// Gets the current number of queued deletes. + public int DeleteCount => _pendingDeletes.Count; + + internal MessageQueue(RestClient rest, Logger logger) + { + _rest = rest; + _logger = logger; + _nextWarning = WarningStart; + + _nonceRand = new Random(); + _pendingSends = new ConcurrentQueue(); + _pendingEdits = new ConcurrentQueue(); + _pendingDeletes = new ConcurrentQueue(); + _pendingSendsByNonce = new ConcurrentDictionary(); + } + + internal Message QueueSend(Channel channel, string text, bool isTTS) + { + Message msg = new Message(0, channel, channel.IsPrivate ? channel.Client.PrivateUser : channel.Server.CurrentUser); + msg.IsTTS = isTTS; + msg.RawText = text; + msg.Text = msg.Resolve(text); + msg.Nonce = GenerateNonce(); + if (_pendingSendsByNonce.TryAdd(msg.Nonce, text)) + { + msg.State = MessageState.Queued; + IncrementCount(); + _pendingSends.Enqueue(msg); + } + else + msg.State = MessageState.Failed; + return msg; + } + internal void QueueEdit(Message msg, string text) + { + string msgText = msg.RawText; + if (msg.State == MessageState.Queued && _pendingSendsByNonce.TryUpdate(msg.Nonce, text, msgText)) + { + //Successfully edited the message before it was sent. + return; + } + IncrementCount(); + _pendingEdits.Enqueue(new MessageEdit(msg, text)); + } + internal void QueueDelete(Message msg) + { + string ignored; + if (msg.State == MessageState.Queued && _pendingSendsByNonce.TryRemove(msg.Nonce, out ignored)) + { + //Successfully stopped the message from being sent + msg.State = MessageState.Aborted; + return; + } + IncrementCount(); + _pendingDeletes.Enqueue(msg); + } + + internal Task[] Run(CancellationToken cancelToken) + { + return new[] + { + RunSendQueue(cancelToken), + RunEditQueue(cancelToken), + RunDeleteQueue(cancelToken) + }; + } + private Task RunSendQueue(CancellationToken cancelToken) + { + return Task.Run(async () => + { + try + { + while (!cancelToken.IsCancellationRequested) + { + Message msg; + while (_pendingSends.TryDequeue(out msg)) + { + DecrementCount(); + string text; + if (_pendingSendsByNonce.TryRemove(msg.Nonce, out text)) //If it was deleted from queue, this will fail + { + try + { + msg.RawText = text; + msg.Text = msg.Resolve(text); + var request = new SendMessageRequest(msg.Channel.Id) + { + Content = msg.RawText, + Nonce = msg.Nonce.ToString(), + IsTTS = msg.IsTTS + }; + var response = await _rest.Send(request).ConfigureAwait(false); + msg.State = MessageState.Normal; + msg.Id = response.Id; + msg.Update(response); + } + catch (Exception ex) + { + msg.State = MessageState.Failed; + _logger.Error($"Failed to send message to {msg.Channel.Path}", ex); + } + } + } + await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + private Task RunEditQueue(CancellationToken cancelToken) + { + return Task.Run(async () => + { + try + { + while (!cancelToken.IsCancellationRequested) + { + MessageEdit edit; + while (_pendingEdits.TryPeek(out edit) && edit.Message.State != MessageState.Queued) + { + if (_pendingEdits.TryDequeue(out edit)) + { + DecrementCount(); + if (edit.Message.State == MessageState.Normal) + { + try + { + var request = new UpdateMessageRequest(edit.Message.Channel.Id, edit.Message.Id) + { + Content = edit.NewText + }; + await _rest.Send(request).ConfigureAwait(false); + } + catch (Exception ex) { _logger.Error($"Failed to edit message {edit.Message.Path}", ex); } + } + } + } + await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + private Task RunDeleteQueue(CancellationToken cancelToken) + { + return Task.Run(async () => + { + try + { + while (!cancelToken.IsCancellationRequested) + { + Message msg; + while (_pendingDeletes.TryPeek(out msg) && msg.State != MessageState.Queued) + { + if (_pendingDeletes.TryDequeue(out msg)) + { + DecrementCount(); + if (msg.State == MessageState.Normal) + { + try + { + var request = new DeleteMessageRequest(msg.Channel.Id, msg.Id); + await _rest.Send(request).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } //Ignore + catch (Exception ex) { _logger.Error($"Failed to delete message {msg.Path}", ex); } + } + } + } + + await Task.Delay(Discord.DiscordConfig.MessageQueueInterval).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + + private void IncrementCount() + { + int count = Interlocked.Increment(ref _count); + if (count >= _nextWarning) + { + _nextWarning <<= 1; + int sendCount = _pendingSends.Count; + int editCount = _pendingEdits.Count; + int deleteCount = _pendingDeletes.Count; + count = sendCount + editCount + deleteCount; //May not add up due to async + _logger.Warning($"Queue is backed up, currently at {count} actions ({sendCount} sends, {editCount} edits, {deleteCount} deletes)."); + } + else if (count < WarningStart) //Reset once the problem is solved + _nextWarning = WarningStart; + } + private void DecrementCount() + { + int count = Interlocked.Decrement(ref _count); + if (count < (WarningStart / 2)) //Reset once the problem is solved + _nextWarning = WarningStart; + } + + /// Clears all queued message sends/edits/deletes. + public void Clear() + { + Message msg; + MessageEdit edit; + + while (_pendingSends.TryDequeue(out msg)) + DecrementCount(); + while (_pendingEdits.TryDequeue(out edit)) + DecrementCount(); + while (_pendingDeletes.TryDequeue(out msg)) + DecrementCount(); + _pendingSendsByNonce.Clear(); + } + + private int GenerateNonce() + { + lock (_nonceRand) + return _nonceRand.Next(1, int.MaxValue); + } + } +} diff --git a/discord.net/src/Discord.Net/Models/Channel.cs b/discord.net/src/Discord.Net/Models/Channel.cs new file mode 100644 index 000000000..5037c88bc --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Channel.cs @@ -0,0 +1,600 @@ +using Discord.API.Client; +using Discord.API.Client.Rest; +using Discord.Net; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using APIChannel = Discord.API.Client.Channel; + +namespace Discord +{ + public class Channel : IMentionable + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + private struct Member + { + public User User { get; } + public ChannelPermissions Permissions { get; } + + public Member(User user, ChannelPermissions permissions) + { + User = user; + Permissions = permissions; + } + } + + public class PermissionOverwrite + { + public PermissionTarget TargetType { get; } + public ulong TargetId { get; } + public ChannelPermissionOverrides Permissions { get; } + + internal PermissionOverwrite(PermissionTarget targetType, ulong targetId, uint allow, uint deny) + { + TargetType = targetType; + TargetId = targetId; + Permissions = new ChannelPermissionOverrides(allow, deny); + } + } + + private readonly ConcurrentDictionary _users; + private readonly ConcurrentDictionary _messages; + private Dictionary _permissionOverwrites; + + public DiscordClient Client { get; } + + /// Gets the unique identifier for this channel. + public ulong Id { get; } + /// Gets the server owning this channel, if this is a public chat. + public Server Server { get; } + /// Gets the target user, if this is a private chat. + public User Recipient { get; } + + /// Gets the name of this channel. + public string Name { get; private set; } + /// Gets the topic of this channel. + public string Topic { get; private set; } + /// Gets the position of this channel relative to other channels in this server. + public int Position { get; private set; } + /// Gets the type of this channel). + public ChannelType Type { get; private set; } + + /// Gets the path to this object. + internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; + /// Gets true if this is a private chat with another user. + public bool IsPrivate => Recipient != null; + /// Gets the string used to mention this channel. + public string Mention => $"<#{Id}>"; + /// Gets a collection of all messages the client has seen posted in this channel. This collection does not guarantee any ordering. + public IEnumerable Messages => _messages?.Values ?? Enumerable.Empty(); + /// Gets a collection of all custom permissions used for this channel. + public IEnumerable PermissionOverwrites => _permissionOverwrites.Select(x => x.Value); + + /// Gets a collection of all users with read access to this channel. + public IEnumerable Users + { + get + { + if (Client.Config.UsePermissionsCache) + { + if (IsPrivate) + return new User[] { Client.PrivateUser, Recipient }; + else if (Type == ChannelType.Text) + return _users.Values.Where(x => x.Permissions.ReadMessages == true).Select(x => x.User); + else if (Type == ChannelType.Voice) + return _users.Values.Select(x => x.User).Where(x => x.VoiceChannel == this); + } + else + { + if (IsPrivate) + return new User[] { Client.PrivateUser, Recipient }; + else if (Type == ChannelType.Text) + { + ChannelPermissions perms = new ChannelPermissions(); + return Server.Users.Where(x => + { + UpdatePermissions(x, ref perms); + return perms.ReadMessages == true; + }); + } + else if (Type == ChannelType.Voice) + return Server.Users.Where(x => x.VoiceChannel == this); + } + return Enumerable.Empty(); + } + } + + internal Channel(DiscordClient client, ulong id, Server server) + : this(client, id) + { + Server = server; + if (client.Config.UsePermissionsCache) + _users = new ConcurrentDictionary(2, (int)(server.UserCount * 1.05)); + } + internal Channel(DiscordClient client, ulong id, User recipient) + : this(client, id) + { + Recipient = recipient; + Type = ChannelType.Text; //Discord doesn't give us a type for private channels + } + private Channel(DiscordClient client, ulong id) + { + Client = client; + Id = id; + + _permissionOverwrites = new Dictionary(); + if (client.Config.MessageCacheSize > 0) + _messages = new ConcurrentDictionary(2, (int)(client.Config.MessageCacheSize * 1.05)); + } + + internal void Update(APIChannel model) + { + if (!IsPrivate && model.Name != null) + Name = model.Name; + if (model.Type != null) + Type = model.Type; + if (model.Position != null) + Position = model.Position.Value; + if (model.Topic != null) + Topic = model.Topic; + if (model.Recipient != null) + { + Recipient.Update(model.Recipient); + Name = $"@{Recipient}"; + } + + if (model.PermissionOverwrites != null) + { + _permissionOverwrites = model.PermissionOverwrites + .Select(x => new PermissionOverwrite(PermissionTarget.FromString(x.Type), x.Id, x.Allow, x.Deny)) + .ToDictionary(x => x.TargetId); + UpdatePermissions(); + } + } + + /// Edits this channel, changing only non-null attributes. + public async Task Edit(string name = null, string topic = null, int? position = null) + { + if (name != null || topic != null) + { + var request = new UpdateChannelRequest(Id) + { + Name = name ?? Name, + Topic = topic ?? Topic, + Position = Position + }; + await Client.ClientAPI.Send(request).ConfigureAwait(false); + } + + if (position != null) + { + Channel[] channels = Server.AllChannels.Where(x => x.Type == Type).OrderBy(x => x.Position).ToArray(); + int oldPos = Array.IndexOf(channels, this); + var newPosChannel = channels.Where(x => x.Position > position).FirstOrDefault(); + int newPos = (newPosChannel != null ? Array.IndexOf(channels, newPosChannel) : channels.Length) - 1; + if (newPos < 0) + newPos = 0; + int minPos; + + if (oldPos < newPos) //Moving Down + { + minPos = oldPos; + for (int i = oldPos; i < newPos; i++) + channels[i] = channels[i + 1]; + channels[newPos] = this; + } + else //(oldPos > newPos) Moving Up + { + minPos = newPos; + for (int i = oldPos; i > newPos; i--) + channels[i] = channels[i - 1]; + channels[newPos] = this; + } + Channel after = minPos > 0 ? channels.Skip(minPos - 1).FirstOrDefault() : null; + await Server.ReorderChannels(channels.Skip(minPos), after).ConfigureAwait(false); + } + } + + public async Task Delete() + { + try { await Client.ClientAPI.Send(new DeleteChannelRequest(Id)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + #region Invites + /// Gets all active (non-expired) invites to this server. + public async Task> GetInvites() + => (await Server.GetInvites().ConfigureAwait(false)).Where(x => x.Channel.Id == Id); + + /// Creates a new invite to this channel. + /// Time (in seconds) until the invite expires. Set to null to never expire. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to null. + public async Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) + { + if (maxAge < 0) throw new ArgumentOutOfRangeException(nameof(maxAge)); + if (maxUses < 0) throw new ArgumentOutOfRangeException(nameof(maxUses)); + + var request = new CreateInviteRequest(Id) + { + MaxAge = maxAge ?? 0, + MaxUses = maxUses ?? 0, + IsTemporary = tempMembership, + WithXkcdPass = withXkcd + }; + + var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); + var invite = new Invite(Client, response.Code, response.XkcdPass); + return invite; + } + #endregion + + #region Messages + internal Message AddMessage(ulong id, User user, DateTime timestamp) + { + Message message = new Message(id, this, user); + message.State = MessageState.Normal; + var cacheLength = Client.Config.MessageCacheSize; + if (cacheLength > 0) + { + var oldestIds = _messages + .Where(x => x.Value.Timestamp < timestamp) + .Select(x => x.Key).OrderBy(x => x) + .Take(_messages.Count - cacheLength); + Message removed; + foreach (var removeId in oldestIds) + _messages.TryRemove(removeId, out removed); + return _messages.GetOrAdd(message.Id, message); + } + return message; + } + internal Message RemoveMessage(ulong id) + { + if (Client.Config.MessageCacheSize > 0) + { + Message msg; + if (_messages.TryRemove(id, out msg)) + return msg; + } + return new Message(id, this, null); + } + + public Message GetMessage(ulong id) + => GetMessage(id, null); + internal Message GetMessage(ulong id, ulong? userId) + { + if (Client.Config.MessageCacheSize > 0) + { + Message result; + if (_messages.TryGetValue(id, out result)) + return result; + } + return new Message(id, this, userId != null ? GetUserFast(userId.Value) : null); + } + + public async Task DownloadMessages(int limit = 100, ulong? relativeMessageId = null, + Relative relativeDir = Relative.Before, bool useCache = true) + { + if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); + if (limit == 0 || Type != ChannelType.Text) return new Message[0]; + + try + { + var request = new GetMessagesRequest(Id) + { + Limit = limit, + RelativeDir = relativeMessageId.HasValue ? relativeDir == Relative.Before ? "before" : "after" : null, + RelativeId = relativeMessageId ?? 0 + }; + var msgs = await Client.ClientAPI.Send(request).ConfigureAwait(false); + return msgs.Select(x => + { + Message msg = null; + if (useCache) + { + msg = AddMessage(x.Id, GetUserFast(x.Author.Id), x.Timestamp.Value); + var user = msg.User; + if (user != null) + user.UpdateActivity(msg.EditedTimestamp ?? msg.Timestamp); + } + else + msg = new Message(x.Id, this, GetUserFast(x.Author.Id)); + msg.Update(x); + return msg; + }) + .ToArray(); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.Forbidden) + { + return new Message[0]; + } + } + + /// Returns all members of this channel with the specified name. + /// Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false. + public IEnumerable FindUsers(string name, bool exactMatch = false) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + return Users.Find(name, exactMatch: exactMatch); + } + + public Task SendMessage(string text) => SendMessageInternal(text, false); + public Task SendTTSMessage(string text) => SendMessageInternal(text, true); + private Task SendMessageInternal(string text, bool isTTS) + { + if (text == null) throw new ArgumentNullException(nameof(text)); + if (text == "") throw new ArgumentException("Value cannot be blank", nameof(text)); + if (text.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); + return Task.FromResult(Client.MessageQueue.QueueSend(this, text, isTTS)); + } + + public async Task SendFile(string filePath) + { + using (var stream = File.OpenRead(filePath)) + return await SendFile(System.IO.Path.GetFileName(filePath), stream).ConfigureAwait(false); + } + public async Task SendFile(string filename, Stream stream) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + if (stream == null) throw new ArgumentNullException(nameof(stream)); + + var request = new SendFileRequest(Id) + { + Filename = filename, + Stream = stream + }; + var model = await Client.ClientAPI.Send(request).ConfigureAwait(false); + + var msg = AddMessage(model.Id, IsPrivate ? Client.PrivateUser : Server.CurrentUser, model.Timestamp.Value); + msg.Update(model); + return msg; + } + + public Task SendIsTyping() => Client.ClientAPI.Send(new SendIsTypingRequest(Id)); + #endregion + + #region Permissions + internal void UpdatePermissions() + { + if (!Client.Config.UsePermissionsCache) return; + + foreach (var pair in _users) + { + var member = pair.Value; + var perms = member.Permissions; + if (UpdatePermissions(member.User, ref perms)) + _users[pair.Key] = new Member(member.User, perms); + } + } + internal void UpdatePermissions(User user) + { + if (!Client.Config.UsePermissionsCache) return; + + Member member; + if (_users.TryGetValue(user.Id, out member)) + { + var perms = member.Permissions; + if (UpdatePermissions(member.User, ref perms)) + _users[user.Id] = new Member(member.User, perms); + } + } + internal bool UpdatePermissions(User user, ref ChannelPermissions permissions) + { + uint newPermissions = 0; + var server = Server; + + //Load the mask of all permissions supported by this channel type + var mask = ChannelPermissions.All(this).RawValue; + + if (server != null) + { + //Start with this user's server permissions + newPermissions = server.GetPermissions(user).RawValue; + + if (IsPrivate || user == Server.Owner || newPermissions.HasBit((byte)PermissionBits.Administrator)) + newPermissions = mask; //Owners and Administrators always have all permissions + else + { + var channelOverwrites = PermissionOverwrites; + + var roles = user.Roles; + foreach (var denyRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.DenyValue != 0 && roles.Any(y => y.Id == x.TargetId))) + newPermissions &= ~denyRole.Permissions.DenyValue; + foreach (var allowRole in channelOverwrites.Where(x => x.TargetType == PermissionTarget.Role && x.Permissions.AllowValue != 0 && roles.Any(y => y.Id == x.TargetId))) + newPermissions |= allowRole.Permissions.AllowValue; + foreach (var denyUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id && x.Permissions.DenyValue != 0)) + newPermissions &= ~denyUser.Permissions.DenyValue; + foreach (var allowUser in channelOverwrites.Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id && x.Permissions.AllowValue != 0)) + newPermissions |= allowUser.Permissions.AllowValue; + + if (Type == ChannelType.Text && !newPermissions.HasBit((byte)PermissionBits.ReadMessages)) + newPermissions = 0; //No read permission on a text channel removes all other permissions + else if (Type == ChannelType.Voice && !newPermissions.HasBit((byte)PermissionBits.Connect)) + newPermissions = 0; //No connect permissions on a voice channel removes all other permissions + else + newPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from serverPerms, for example) + } + } + else + newPermissions = mask; //Private messages always have all permissions + + if (newPermissions != permissions.RawValue) + { + permissions = new ChannelPermissions(newPermissions); + return true; + } + return false; + } + internal ChannelPermissions GetPermissions(User user) + { + if (Client.Config.UsePermissionsCache) + { + Member member; + if (_users.TryGetValue(user.Id, out member)) + return member.Permissions; + else + return ChannelPermissions.None; + } + else + { + ChannelPermissions perms = new ChannelPermissions(); + UpdatePermissions(user, ref perms); + return perms; + } + } + + public ChannelPermissionOverrides GetPermissionsRule(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + return PermissionOverwrites + .Where(x => x.TargetType == PermissionTarget.User && x.TargetId == user.Id) + .Select(x => x.Permissions) + .FirstOrDefault(); + } + public ChannelPermissionOverrides GetPermissionsRule(Role role) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + + return PermissionOverwrites + .Where(x => x.TargetType == PermissionTarget.Role && x.TargetId == role.Id) + .Select(x => x.Permissions) + .FirstOrDefault(); + } + + public Task AddPermissionsRule(User user, ChannelPermissions allow, ChannelPermissions deny) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + return AddPermissionsRule(user.Id, PermissionTarget.User, allow.RawValue, deny.RawValue); + } + public Task AddPermissionsRule(User user, ChannelPermissionOverrides permissions) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + + return AddPermissionsRule(user.Id, PermissionTarget.User, permissions.AllowValue, permissions.DenyValue); + } + public Task AddPermissionsRule(Role role, ChannelPermissions allow, ChannelPermissions deny) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + + return AddPermissionsRule(role.Id, PermissionTarget.Role, allow.RawValue, deny.RawValue); + } + public Task AddPermissionsRule(Role role, ChannelPermissionOverrides permissions) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + + return AddPermissionsRule(role.Id, PermissionTarget.Role, permissions.AllowValue, permissions.DenyValue); + } + private Task AddPermissionsRule(ulong targetId, PermissionTarget targetType, uint allow, uint deny) + { + var request = new AddChannelPermissionsRequest(Id) + { + TargetId = targetId, + TargetType = targetType.Value, + Allow = allow, + Deny = deny + }; + return Client.ClientAPI.Send(request); + } + + public Task RemovePermissionsRule(User user) + { + if (user == null) throw new ArgumentNullException(nameof(user)); + return RemovePermissionsRule(user.Id, PermissionTarget.User); + } + public Task RemovePermissionsRule(Role role) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + return RemovePermissionsRule(role.Id, PermissionTarget.Role); + } + private async Task RemovePermissionsRule(ulong userOrRoleId, PermissionTarget targetType) + { + try + { + var perms = PermissionOverwrites.Where(x => x.TargetType != targetType || x.TargetId != userOrRoleId).FirstOrDefault(); + await Client.ClientAPI.Send(new RemoveChannelPermissionsRequest(Id, userOrRoleId)).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + #endregion + + #region Users + internal void AddUser(User user) + { + if (!Client.Config.UsePermissionsCache) return; + + var perms = new ChannelPermissions(); + UpdatePermissions(user, ref perms); + var member = new Member(user, ChannelPermissions.None); + _users[user.Id] = new Member(user, ChannelPermissions.None); + } + internal void RemoveUser(ulong id) + { + if (!Client.Config.UsePermissionsCache) return; + + Member ignored; + _users.TryRemove(id, out ignored); + } + public User GetUser(ulong id) + { + if (IsPrivate) + { + if (id == Recipient.Id) + return Recipient; + else if (id == Client.PrivateUser.Id) + return Client.PrivateUser; + } + else if (!Client.Config.UsePermissionsCache) + { + var user = Server.GetUser(id); + if (user != null) + { + ChannelPermissions perms = new ChannelPermissions(); + UpdatePermissions(user, ref perms); + if (perms.ReadMessages) + return user; + } + } + else + { + Member result; + if (_users.TryGetValue(id, out result)) + return result.User; + } + return null; + } + internal User GetUserFast(ulong id) + { + //Can return users not in this channel, but that's usually okay + if (IsPrivate) + { + if (id == Recipient.Id) + return Recipient; + else if (id == Client.PrivateUser.Id) + return Client.PrivateUser; + } + else + return Server.GetUser(id); + return null; + } + #endregion + + internal Channel Clone() + { + var result = new Channel(); + _cloner(this, result); + return result; + } + private Channel() { } + + public override string ToString() => Name ?? Id.ToIdString(); + } +} diff --git a/discord.net/src/Discord.Net/Models/Color.cs b/discord.net/src/Discord.Net/Models/Color.cs new file mode 100644 index 000000000..3555c5d8d --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Color.cs @@ -0,0 +1,46 @@ +namespace Discord +{ + public class Color + { + public static readonly Color Default = new Color(0); + + public static readonly Color Teal = new Color(0x1ABC9C); + public static readonly Color DarkTeal = new Color(0x11806A); + public static readonly Color Green = new Color(0x2ECC71); + public static readonly Color DarkGreen = new Color(0x1F8B4C); + public static readonly Color Blue = new Color(0x3498DB); + public static readonly Color DarkBlue = new Color(0x206694); + public static readonly Color Purple = new Color(0x9B59B6); + public static readonly Color DarkPurple = new Color(0x71368A); + public static readonly Color Magenta = new Color(0xE91E63); + public static readonly Color DarkMagenta = new Color(0xAD1457); + public static readonly Color Gold = new Color(0xF1C40F); + public static readonly Color DarkGold = new Color(0xC27C0E); + public static readonly Color Orange = new Color(0xE67E22); + public static readonly Color DarkOrange = new Color(0xA84300); + public static readonly Color Red = new Color(0xE74C3C); + public static readonly Color DarkRed = new Color(0x992D22); + + public static readonly Color LighterGrey = new Color(0x95A5A6); + public static readonly Color DarkGrey = new Color(0x607D8B); + public static readonly Color LightGrey = new Color(0x979C9F); + public static readonly Color DarkerGrey = new Color(0x546E7A); + + public uint RawValue { get; } + + public Color(uint rawValue) { RawValue = rawValue; } + public Color(byte r, byte g, byte b) : this(((uint)r << 16) | ((uint)g << 8) | b) { } + public Color(float r, float g, float b) : this((byte)(r * 255.0f), (byte)(g * 255.0f), (byte)(b * 255.0f)) { } + + /// Gets or sets the red component for this color. + public byte R => (byte)(RawValue >> 16); + /// Gets or sets the green component for this color. + public byte G => (byte)(RawValue >> 8); + /// Gets or sets the blue component for this color. + public byte B => (byte)(RawValue); + + private byte GetByte(int pos) => (byte)(RawValue >> (8 * (pos - 1))); + + public override string ToString() => '#' + RawValue.ToString("X"); + } +} diff --git a/discord.net/src/Discord.Net/Models/Game.cs b/discord.net/src/Discord.Net/Models/Game.cs new file mode 100644 index 000000000..0c7c25be3 --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Game.cs @@ -0,0 +1,22 @@ +namespace Discord +{ + public struct Game + { + public string Name { get; } + public string Url { get; } + public GameType Type { get; } + + public Game(string name) + { + Name = name; + Url = null; + Type = GameType.Default; + } + public Game(string name, GameType type, string url) + { + Name = name; + Url = url; + Type = type; + } + } +} diff --git a/discord.net/src/Discord.Net/Models/Invite.cs b/discord.net/src/Discord.Net/Models/Invite.cs new file mode 100644 index 000000000..a6a0407b1 --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Invite.cs @@ -0,0 +1,143 @@ +using Discord.API.Client; +using Discord.API.Client.Rest; +using Discord.Net; +using System; +using System.Net; +using System.Threading.Tasks; +using APIInvite = Discord.API.Client.Invite; + +namespace Discord +{ + public class Invite + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + public class ServerInfo + { + /// Returns the unique identifier of this server. + public ulong Id { get; } + /// Returns the name of this server. + public string Name { get; } + + internal ServerInfo(ulong id, string name) + { + Id = id; + Name = name; + } + } + public class ChannelInfo + { + /// Returns the unique identifier of this channel. + public ulong Id { get; } + /// Returns the name of this channel. + public string Name { get; } + + internal ChannelInfo(ulong id, string name) + { + Id = id; + Name = name; + } + } + public class InviterInfo + { + /// Returns the unique identifier for this user. + public ulong Id { get; } + /// Returns the name of this user. + public string Name { get; } + /// Returns the by-name unique identifier for this user. + public ushort Discriminator { get; } + /// Returns the unique identifier for this user's avatar. + public string AvatarId { get; } + + /// Returns the full path to this user's avatar. + public string AvatarUrl => User.GetAvatarUrl(Id, AvatarId); + + internal InviterInfo(ulong id, string name, ushort discriminator, string avatarId) + { + Id = id; + Name = name; + Discriminator = discriminator; + AvatarId = avatarId; + } + } + + public DiscordClient Client { get; } + + /// Gets the unique code for this invite. + public string Code { get; } + /// Gets, if enabled, an alternative human-readable invite code. + public string XkcdCode { get; } + + /// Gets information about the server this invite is attached to. + public ServerInfo Server { get; private set; } + /// Gets information about the channel this invite is attached to. + public ChannelInfo Channel { get; private set; } + /// Gets the time (in seconds) until the invite expires. + public int? MaxAge { get; private set; } + /// Gets the amount of times this invite has been used. + public int Uses { get; private set; } + /// Gets the max amount of times this invite may be used. + public int? MaxUses { get; private set; } + /// Returns true if this invite has expired, been destroyed, or you are banned from that server. + public bool IsRevoked { get; private set; } + /// If true, a user accepting this invite will be kicked from the server after closing their client. + public bool IsTemporary { get; private set; } + /// Gets when this invite was created. + public DateTime CreatedAt { get; private set; } + + /// Gets the path to this object. + internal string Path => $"{Server?.Name ?? "[Private]"}/{Code}"; + /// Returns a URL for this invite using XkcdCode if available or Id if not. + public string Url => $"{DiscordConfig.InviteUrl}/{Code}"; + + internal Invite(DiscordClient client, string code, string xkcdPass) + { + Client = client; + Code = code; + XkcdCode = xkcdPass; + } + + internal void Update(InviteReference model) + { + if (model.Guild != null) + Server = new ServerInfo(model.Guild.Id, model.Guild.Name); + if (model.Channel != null) + Channel = new ChannelInfo(model.Channel.Id, model.Channel.Name); + } + internal void Update(APIInvite model) + { + Update(model as InviteReference); + + if (model.IsRevoked != null) + IsRevoked = model.IsRevoked.Value; + if (model.IsTemporary != null) + IsTemporary = model.IsTemporary.Value; + if (model.MaxAge != null) + MaxAge = model.MaxAge.Value != 0 ? model.MaxAge.Value : (int?)null; + if (model.MaxUses != null) + MaxUses = model.MaxUses.Value; + if (model.Uses != null) + Uses = model.Uses.Value; + if (model.CreatedAt != null) + CreatedAt = model.CreatedAt.Value; + } + + public async Task Delete() + { + try { await Client.ClientAPI.Send(new DeleteInviteRequest(Code)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + public Task Accept() + => Client.ClientAPI.Send(new AcceptInviteRequest(Code)); + + internal Invite Clone() + { + var result = new Invite(); + _cloner(this, result); + return result; + } + private Invite() { } //Used for cloning + + public override string ToString() => XkcdCode ?? Code; + } +} diff --git a/discord.net/src/Discord.Net/Models/Message.cs b/discord.net/src/Discord.Net/Models/Message.cs new file mode 100644 index 000000000..b0c993413 --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Message.cs @@ -0,0 +1,380 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using APIMessage = Discord.API.Client.Message; + +namespace Discord +{ + public enum MessageState : byte + { + /// Message did not originate from this session, or was successfully sent. + Normal = 0, + /// Message is current queued. + Queued, + /// Message was deleted before it was sent. + Aborted, + /// Message failed to be sent. + Failed + } + + public class Message + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + private static readonly Regex _userRegex = new Regex(@"<@[0-9]+>", RegexOptions.Compiled); + private static readonly Regex _userNicknameRegex = new Regex(@"<@![0-9]+>", RegexOptions.Compiled); + private static readonly Regex _channelRegex = new Regex(@"<#[0-9]+>", RegexOptions.Compiled); + private static readonly Regex _roleRegex = new Regex(@"<@&[0-9]+>", RegexOptions.Compiled); + private static readonly Attachment[] _initialAttachments = new Attachment[0]; + private static readonly Embed[] _initialEmbeds = new Embed[0]; + + internal static string CleanUserMentions(Channel channel, string text, List users = null) + { + ulong id; + text = _userNicknameRegex.Replace(text, new MatchEvaluator(e => + { + if (e.Value.Substring(3, e.Value.Length - 4).TryToId(out id)) + { + var user = channel.GetUserFast(id); + if (user != null) + { + if (users != null) + users.Add(user); + return '@' + user.Nickname; + } + } + return e.Value; //User not found or parse failed + })); + return _userRegex.Replace(text, new MatchEvaluator(e => + { + if (e.Value.Substring(2, e.Value.Length - 3).TryToId(out id)) + { + var user = channel.GetUserFast(id); + if (user != null) + { + if (users != null) + users.Add(user); + return '@' + user.Name; + } + } + return e.Value; //User not found or parse failed + })); + } + internal static string CleanChannelMentions(Channel channel, string text, List channels = null) + { + var server = channel.Server; + if (server == null) return text; + + return _channelRegex.Replace(text, new MatchEvaluator(e => + { + ulong id; + if (e.Value.Substring(2, e.Value.Length - 3).TryToId(out id)) + { + var mentionedChannel = server.GetChannel(id); + if (mentionedChannel != null && mentionedChannel.Server.Id == server.Id) + { + if (channels != null) + channels.Add(mentionedChannel); + return '#' + mentionedChannel.Name; + } + } + return e.Value; //Channel not found or parse failed + })); + } + internal static string CleanRoleMentions(Channel channel, string text, List roles = null) + { + var server = channel.Server; + if (server == null) return text; + + return _roleRegex.Replace(text, new MatchEvaluator(e => + { + ulong id; + if (e.Value.Substring(3, e.Value.Length - 4).TryToId(out id)) + { + var role = server.GetRole(id); + if (role != null) + { + if (roles != null) + roles.Add(role); + return "@" + role.Name; + } + } + return e.Value; //Role not found or parse failed + })); + } + + //TODO: Move this somewhere + private static string Resolve(Channel channel, string text) + { + if (text == null) throw new ArgumentNullException(nameof(text)); + + text = CleanUserMentions(channel, text); + text = CleanChannelMentions(channel, text); + text = CleanRoleMentions(channel, text); + return text; + } + + /*internal class ImportResolver : DefaultContractResolver + { + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + if (member is PropertyInfo) + { + if (member.Name == nameof(ChannelId) || !(member as PropertyInfo).CanWrite) + return null; + + property.Writable = true; //Handles private setters + } + return property; + } + }*/ + + public class Attachment : File + { + /// Unique identifier for this file. + public string Id { get; internal set; } + /// Size, in bytes, of this file file. + public int Size { get; internal set; } + /// Filename of this file. + public string Filename { get; internal set; } + + internal Attachment() { } + } + + public class Embed + { + /// URL of this embed. + public string Url { get; internal set; } + /// Type of this embed. + public string Type { get; internal set; } + /// Title for this embed. + public string Title { get; internal set; } + /// Summary of this embed. + public string Description { get; internal set; } + /// Returns information about the author of this embed. + public EmbedLink Author { get; internal set; } + /// Returns information about the providing website of this embed. + public EmbedLink Provider { get; internal set; } + /// Returns the thumbnail of this embed. + public File Thumbnail { get; internal set; } + /// Returns the video information of this embed. + public File Video { get; internal set; } + + internal Embed() { } + } + + public class EmbedLink + { + /// URL of this embed provider. + public string Url { get; internal set; } + /// Name of this embed provider. + public string Name { get; internal set; } + + internal EmbedLink() { } + } + + public class File + { + /// Download url for this file. + public string Url { get; internal set; } + /// Preview url for this file. + public string ProxyUrl { get; internal set; } + /// Width of this file, if it is an image. + public int? Width { get; internal set; } + /// Height of this file, if it is an image. + public int? Height { get; internal set; } + + internal File() { } + } + + public DiscordClient Client => Channel.Client; + + /// Returns the unique identifier for this message. + public ulong Id { get; internal set; } + /// Returns the channel this message was sent to. + public Channel Channel { get; } + /// Returns the author of this message. + public User User { get; } + + /// Returns true if the message was sent as text-to-speech by someone with permissions to do so. + public bool IsTTS { get; internal set; } + /// Returns the state of this message. Only useful if UseMessageQueue is true. + public MessageState State { get; internal set; } + /// Returns the raw content of this message as it was received from the server. + public string RawText { get; internal set; } + /// Returns the content of this message with any special references such as mentions converted. + public string Text { get; internal set; } + /// Returns the timestamp for when this message was sent. + public DateTime Timestamp { get; private set; } + /// Returns the timestamp for when this message was last edited. + public DateTime? EditedTimestamp { get; private set; } + /// Returns the attachments included in this message. + public Attachment[] Attachments { get; private set; } + /// Returns a collection of all embeded content in this message. + public Embed[] Embeds { get; private set; } + + /// Returns a collection of all users mentioned in this message. + public IEnumerable MentionedUsers { get; internal set; } + /// Returns a collection of all channels mentioned in this message. + public IEnumerable MentionedChannels { get; internal set; } + /// Returns a collection of all roles mentioned in this message. + public IEnumerable MentionedRoles { get; internal set; } + + internal int Nonce { get; set; } + + /// Gets the path to this object. + internal string Path => $"{Server?.Name ?? "[Private]"}/{Id}"; + /// Returns the server containing the channel this message was sent to. + public Server Server => Channel.Server; + /// Returns if this message was sent from the logged-in accounts. + public bool IsAuthor => User != null && User.Id == Client.CurrentUser?.Id; + + internal Message(ulong id, Channel channel, User user) + { + Id = id; + Channel = channel; + User = user; + + Attachments = _initialAttachments; + Embeds = _initialEmbeds; + } + + internal void Update(APIMessage model) + { + var channel = Channel; + var server = channel.Server; + if (model.Attachments != null) + { + Attachments = model.Attachments + .Select(x => new Attachment() + { + Id = x.Id, + Url = x.Url, + ProxyUrl = x.ProxyUrl, + Width = x.Width, + Height = x.Height, + Size = x.Size, + Filename = x.Filename + }) + .ToArray(); + } + if (model.Embeds != null) + { + Embeds = model.Embeds.Select(x => + { + EmbedLink author = null, provider = null; + File thumbnail = null, video = null; + + if (x.Author != null) + author = new EmbedLink { Url = x.Author.Url, Name = x.Author.Name }; + if (x.Provider != null) + provider = new EmbedLink { Url = x.Provider.Url, Name = x.Provider.Name }; + if (x.Thumbnail != null) + thumbnail = new File { Url = x.Thumbnail.Url, ProxyUrl = x.Thumbnail.ProxyUrl, Width = x.Thumbnail.Width, Height = x.Thumbnail.Height }; + if (x.Video != null) + video = new File { Url = x.Video.Url, ProxyUrl = null, Width = x.Video.Width, Height = x.Video.Height }; + + return new Embed + { + Url = x.Url, + Type = x.Type, + Title = x.Title, + Description = x.Description, + Author = author, + Provider = provider, + Thumbnail = thumbnail, + Video = video + }; + }).ToArray(); + } + + if (model.IsTextToSpeech != null) + IsTTS = model.IsTextToSpeech.Value; + if (model.Timestamp != null) + Timestamp = model.Timestamp.Value; + if (model.EditedTimestamp != null) + EditedTimestamp = model.EditedTimestamp; + if (model.Mentions != null) + { + MentionedUsers = model.Mentions + .Select(x => Channel.GetUserFast(x.Id)) + .Where(x => x != null) + .ToArray(); + } + + if (model.Content != null) + { + string text = model.Content; + RawText = text; + //var mentionedUsers = new List(); + var mentionedChannels = new List(); + var mentionedRoles = new List(); + text = CleanUserMentions(Channel, text/*, mentionedUsers*/); + if (server != null) + { + text = CleanChannelMentions(Channel, text, mentionedChannels); + text = CleanRoleMentions(Channel, text, mentionedRoles); + if (model.IsMentioningEveryone != null && model.IsMentioningEveryone.Value + && User != null && User.GetPermissions(channel).MentionEveryone) + mentionedRoles.Add(Server.EveryoneRole); + } + Text = text; + + //MentionedUsers = mentionedUsers; + MentionedChannels = mentionedChannels; + MentionedRoles = mentionedRoles; + } + } + + public Task Edit(string text) + { + if (text == null) throw new ArgumentNullException(nameof(text)); + + var channel = Channel; + + if (text.Length > DiscordConfig.MaxMessageSize) + throw new ArgumentOutOfRangeException(nameof(text), $"Message must be {DiscordConfig.MaxMessageSize} characters or less."); + + Client.MessageQueue.QueueEdit(this, text); + return TaskHelper.CompletedTask; + } + public Task Delete() + { + Client.MessageQueue.QueueDelete(this); + return TaskHelper.CompletedTask; + } + + /// Returns true if the logged-in user was mentioned. + public bool IsMentioningMe(bool includeRoles = false) + { + User me = Server != null ? Server.CurrentUser : Channel.Client.PrivateUser; + if (includeRoles) + { + return (MentionedUsers?.Contains(me) ?? false) || + (MentionedRoles?.Any(x => me.HasRole(x)) ?? false); + } + else + return MentionedUsers?.Contains(me) ?? false; + } + + /// Resolves all mentions in a provided string to those users, channels or roles' names. + public string Resolve(string text) + { + if (text == null) throw new ArgumentNullException(nameof(text)); + return Resolve(Channel, text); + } + + internal Message Clone() + { + var result = new Message(); + _cloner(this, result); + return result; + } + private Message() { } //Used for cloning + + public override string ToString() => $"{User?.Name ?? "Unknown User"}: {RawText}"; + } +} diff --git a/discord.net/src/Discord.Net/Models/Permissions.cs b/discord.net/src/Discord.Net/Models/Permissions.cs new file mode 100644 index 000000000..d873355a3 --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Permissions.cs @@ -0,0 +1,355 @@ +using System; +using System.Runtime.CompilerServices; + +namespace Discord +{ + public struct ServerPermissions + { + public static ServerPermissions None { get; } = new ServerPermissions(); + public static ServerPermissions All { get; } = new ServerPermissions(Convert.ToUInt32("00011111111100111111110000111111", 2)); + + public uint RawValue { get; } + + /// If True, a user may create invites. + public bool CreateInstantInvite => PermissionsHelper.GetValue(RawValue, PermissionBits.CreateInstantInvite); + /// If True, a user may ban users from the server. + public bool BanMembers => PermissionsHelper.GetValue(RawValue, PermissionBits.BanMembers); + /// If True, a user may kick users from the server. + public bool KickMembers => PermissionsHelper.GetValue(RawValue, PermissionBits.KickMembers); + /// If True, a user has all permissions and cannot have them revoked. + public bool Administrator => PermissionsHelper.GetValue(RawValue, PermissionBits.Administrator); + /// If True, a user may create, delete and modify channels. + public bool ManageChannels => PermissionsHelper.GetValue(RawValue, PermissionBits.ManageChannel); + /// If True, a user may adjust server properties. + public bool ManageServer => PermissionsHelper.GetValue(RawValue, PermissionBits.ManageServer); + + /// If True, a user may join channels. + public bool ReadMessages => PermissionsHelper.GetValue(RawValue, PermissionBits.ReadMessages); + /// If True, a user may send messages. + public bool SendMessages => PermissionsHelper.GetValue(RawValue, PermissionBits.SendMessages); + /// If True, a user may send text-to-speech messages. + public bool SendTTSMessages => PermissionsHelper.GetValue(RawValue, PermissionBits.SendTTSMessages); + /// If True, a user may delete messages. + public bool ManageMessages => PermissionsHelper.GetValue(RawValue, PermissionBits.ManageMessages); + /// If True, Discord will auto-embed links sent by this user. + public bool EmbedLinks => PermissionsHelper.GetValue(RawValue, PermissionBits.EmbedLinks); + /// If True, a user may send files. + public bool AttachFiles => PermissionsHelper.GetValue(RawValue, PermissionBits.AttachFiles); + /// If True, a user may read previous messages. + public bool ReadMessageHistory => PermissionsHelper.GetValue(RawValue, PermissionBits.ReadMessageHistory); + /// If True, a user may mention @everyone. + public bool MentionEveryone => PermissionsHelper.GetValue(RawValue, PermissionBits.MentionEveryone); + + /// If True, a user may connect to a voice channel. + public bool Connect => PermissionsHelper.GetValue(RawValue, PermissionBits.Connect); + /// If True, a user may speak in a voice channel. + public bool Speak => PermissionsHelper.GetValue(RawValue, PermissionBits.Speak); + /// If True, a user may mute users. + public bool MuteMembers => PermissionsHelper.GetValue(RawValue, PermissionBits.MuteMembers); + /// If True, a user may deafen users. + public bool DeafenMembers => PermissionsHelper.GetValue(RawValue, PermissionBits.DeafenMembers); + /// If True, a user may move other users between voice channels. + public bool MoveMembers => PermissionsHelper.GetValue(RawValue, PermissionBits.MoveMembers); + /// If True, a user may use voice activation rather than push-to-talk. + public bool UseVoiceActivation => PermissionsHelper.GetValue(RawValue, PermissionBits.UseVoiceActivation); + + /// If True, a user may change their own nickname. + public bool ChangeNickname => PermissionsHelper.GetValue(RawValue, PermissionBits.ChangeNickname); + /// If True, a user may change the nickname of other users. + public bool ManageNicknames => PermissionsHelper.GetValue(RawValue, PermissionBits.ManageNicknames); + /// If True, a user may adjust roles. + public bool ManageRoles => PermissionsHelper.GetValue(RawValue, PermissionBits.ManageRolesOrPermissions); + + public ServerPermissions(bool? createInstantInvite = null, bool? administrator = null, + bool? banMembers = null, bool? kickMembers = null, bool? manageChannel = null, bool? manageServer = null, + bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, + bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null) + : this(new ServerPermissions(), createInstantInvite, administrator, banMembers, kickMembers, manageChannel, manageServer, readMessages, + sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, mentionEveryone, connect, speak, muteMembers, deafenMembers, + moveMembers, useVoiceActivation, manageRoles) + { + } + public ServerPermissions(ServerPermissions basePerms, bool? createInstantInvite = null, bool? administrator = null, + bool? banMembers = null, bool? kickMembers = null, bool? manageChannel = null, bool? manageServer = null, + bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, bool? manageMessages = null, + bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, bool? mentionEveryone = null, + bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null, bool? changeNickname = null, bool? manageNicknames = null, bool? manageRoles = null) + { + uint value = basePerms.RawValue; + + PermissionsHelper.SetValue(ref value, createInstantInvite, PermissionBits.CreateInstantInvite); + PermissionsHelper.SetValue(ref value, administrator, PermissionBits.Administrator); + PermissionsHelper.SetValue(ref value, banMembers, PermissionBits.BanMembers); + PermissionsHelper.SetValue(ref value, kickMembers, PermissionBits.KickMembers); + PermissionsHelper.SetValue(ref value, manageChannel, PermissionBits.ManageChannel); + PermissionsHelper.SetValue(ref value, manageServer, PermissionBits.ManageServer); + PermissionsHelper.SetValue(ref value, readMessages, PermissionBits.ReadMessages); + PermissionsHelper.SetValue(ref value, sendMessages, PermissionBits.SendMessages); + PermissionsHelper.SetValue(ref value, sendTTSMessages, PermissionBits.SendTTSMessages); + PermissionsHelper.SetValue(ref value, manageMessages, PermissionBits.ManageMessages); + PermissionsHelper.SetValue(ref value, embedLinks, PermissionBits.EmbedLinks); + PermissionsHelper.SetValue(ref value, attachFiles, PermissionBits.AttachFiles); + PermissionsHelper.SetValue(ref value, readMessageHistory, PermissionBits.ReadMessageHistory); + PermissionsHelper.SetValue(ref value, mentionEveryone, PermissionBits.MentionEveryone); + PermissionsHelper.SetValue(ref value, connect, PermissionBits.Connect); + PermissionsHelper.SetValue(ref value, speak, PermissionBits.Speak); + PermissionsHelper.SetValue(ref value, muteMembers, PermissionBits.MuteMembers); + PermissionsHelper.SetValue(ref value, deafenMembers, PermissionBits.DeafenMembers); + PermissionsHelper.SetValue(ref value, moveMembers, PermissionBits.MoveMembers); + PermissionsHelper.SetValue(ref value, useVoiceActivation, PermissionBits.UseVoiceActivation); + PermissionsHelper.SetValue(ref value, changeNickname, PermissionBits.ChangeNickname); + PermissionsHelper.SetValue(ref value, manageNicknames, PermissionBits.ManageNicknames); + PermissionsHelper.SetValue(ref value, manageRoles, PermissionBits.ManageRolesOrPermissions); + + RawValue = value; + } + public ServerPermissions(uint rawValue) { RawValue = rawValue; } + } + + public struct ChannelPermissions + { + public static ChannelPermissions None { get; } = new ChannelPermissions(); + public static ChannelPermissions TextOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00010000000000111111110000011001", 2)); + public static ChannelPermissions PrivateOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00000000000000011100110000000000", 2)); + public static ChannelPermissions VoiceOnly { get; } = new ChannelPermissions(Convert.ToUInt32("00010011111100000000000000011001", 2)); + public static ChannelPermissions All(Channel channel) => All(channel.Type, channel.IsPrivate); + public static ChannelPermissions All(ChannelType channelType, bool isPrivate) + { + if (isPrivate) return PrivateOnly; + else if (channelType == ChannelType.Text) return TextOnly; + else if (channelType == ChannelType.Voice) return VoiceOnly; + else return None; + } + + public uint RawValue { get; } + + /// If True, a user may create invites. + public bool CreateInstantInvite => PermissionsHelper.GetValue(RawValue, PermissionBits.CreateInstantInvite); + /// If True, a user may create, delete and modify this channel. + public bool ManageChannel => PermissionsHelper.GetValue(RawValue, PermissionBits.ManageChannel); + + /// If True, a user may join channels. + public bool ReadMessages => PermissionsHelper.GetValue(RawValue, PermissionBits.ReadMessages); + /// If True, a user may send messages. + public bool SendMessages => PermissionsHelper.GetValue(RawValue, PermissionBits.SendMessages); + /// If True, a user may send text-to-speech messages. + public bool SendTTSMessages => PermissionsHelper.GetValue(RawValue, PermissionBits.SendTTSMessages); + /// If True, a user may delete messages. + public bool ManageMessages => PermissionsHelper.GetValue(RawValue, PermissionBits.ManageMessages); + /// If True, Discord will auto-embed links sent by this user. + public bool EmbedLinks => PermissionsHelper.GetValue(RawValue, PermissionBits.EmbedLinks); + /// If True, a user may send files. + public bool AttachFiles => PermissionsHelper.GetValue(RawValue, PermissionBits.AttachFiles); + /// If True, a user may read previous messages. + public bool ReadMessageHistory => PermissionsHelper.GetValue(RawValue, PermissionBits.ReadMessageHistory); + /// If True, a user may mention @everyone. + public bool MentionEveryone => PermissionsHelper.GetValue(RawValue, PermissionBits.MentionEveryone); + + /// If True, a user may connect to a voice channel. + public bool Connect => PermissionsHelper.GetValue(RawValue, PermissionBits.Connect); + /// If True, a user may speak in a voice channel. + public bool Speak => PermissionsHelper.GetValue(RawValue, PermissionBits.Speak); + /// If True, a user may mute users. + public bool MuteMembers => PermissionsHelper.GetValue(RawValue, PermissionBits.MuteMembers); + /// If True, a user may deafen users. + public bool DeafenMembers => PermissionsHelper.GetValue(RawValue, PermissionBits.DeafenMembers); + /// If True, a user may move other users between voice channels. + public bool MoveMembers => PermissionsHelper.GetValue(RawValue, PermissionBits.MoveMembers); + /// If True, a user may use voice activation rather than push-to-talk. + public bool UseVoiceActivation => PermissionsHelper.GetValue(RawValue, PermissionBits.UseVoiceActivation); + + /// If True, a user may adjust permissions. + public bool ManagePermissions => PermissionsHelper.GetValue(RawValue, PermissionBits.ManageRolesOrPermissions); + + public ChannelPermissions(bool? createInstantInvite = null, + bool? manageChannel = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, + bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, + bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) + : this(new ChannelPermissions(), createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, + manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, + moveMembers, useVoiceActivation, managePermissions) + { + } + public ChannelPermissions(ChannelPermissions basePerms, bool? createInstantInvite = null, + bool? manageChannel = null, bool? readMessages = null, bool? sendMessages = null, bool? sendTTSMessages = null, + bool? manageMessages = null, bool? embedLinks = null, bool? attachFiles = null, bool? readMessageHistory = null, + bool? mentionEveryone = null, bool? connect = null, bool? speak = null, bool? muteMembers = null, bool? deafenMembers = null, + bool? moveMembers = null, bool? useVoiceActivation = null, bool? managePermissions = null) + { + uint value = basePerms.RawValue; + + PermissionsHelper.SetValue(ref value, createInstantInvite, PermissionBits.CreateInstantInvite); + PermissionsHelper.SetValue(ref value, manageChannel, PermissionBits.ManageChannel); + PermissionsHelper.SetValue(ref value, readMessages, PermissionBits.ReadMessages); + PermissionsHelper.SetValue(ref value, sendMessages, PermissionBits.SendMessages); + PermissionsHelper.SetValue(ref value, sendTTSMessages, PermissionBits.SendTTSMessages); + PermissionsHelper.SetValue(ref value, manageMessages, PermissionBits.ManageMessages); + PermissionsHelper.SetValue(ref value, embedLinks, PermissionBits.EmbedLinks); + PermissionsHelper.SetValue(ref value, attachFiles, PermissionBits.AttachFiles); + PermissionsHelper.SetValue(ref value, readMessageHistory, PermissionBits.ReadMessageHistory); + PermissionsHelper.SetValue(ref value, mentionEveryone, PermissionBits.MentionEveryone); + PermissionsHelper.SetValue(ref value, connect, PermissionBits.Connect); + PermissionsHelper.SetValue(ref value, speak, PermissionBits.Speak); + PermissionsHelper.SetValue(ref value, muteMembers, PermissionBits.MuteMembers); + PermissionsHelper.SetValue(ref value, deafenMembers, PermissionBits.DeafenMembers); + PermissionsHelper.SetValue(ref value, moveMembers, PermissionBits.MoveMembers); + PermissionsHelper.SetValue(ref value, useVoiceActivation, PermissionBits.UseVoiceActivation); + PermissionsHelper.SetValue(ref value, managePermissions, PermissionBits.ManageRolesOrPermissions); + + RawValue = value; + } + public ChannelPermissions(uint rawValue) { RawValue = rawValue; } + } + + public struct ChannelPermissionOverrides + { + public static ChannelPermissionOverrides InheritAll { get; } = new ChannelPermissionOverrides(); + + public uint AllowValue { get; } + public uint DenyValue { get; } + + /// If True, a user may create invites. + public PermValue CreateInstantInvite => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.CreateInstantInvite); + /// If True, a user may create, delete and modify this channel. + public PermValue ManageChannel => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.ManageChannel); + /// If True, a user may join channels. + public PermValue ReadMessages => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.ReadMessages); + /// If True, a user may send messages. + public PermValue SendMessages => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.SendMessages); + /// If True, a user may send text-to-speech messages. + public PermValue SendTTSMessages => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.SendTTSMessages); + /// If True, a user may delete messages. + public PermValue ManageMessages => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.ManageMessages); + /// If True, Discord will auto-embed links sent by this user. + public PermValue EmbedLinks => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.EmbedLinks); + /// If True, a user may send files. + public PermValue AttachFiles => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.AttachFiles); + /// If True, a user may read previous messages. + public PermValue ReadMessageHistory => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.ReadMessageHistory); + /// If True, a user may mention @everyone. + public PermValue MentionEveryone => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.MentionEveryone); + + /// If True, a user may connect to a voice channel. + public PermValue Connect => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.Connect); + /// If True, a user may speak in a voice channel. + public PermValue Speak => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.Speak); + /// If True, a user may mute users. + public PermValue MuteMembers => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.MuteMembers); + /// If True, a user may deafen users. + public PermValue DeafenMembers => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.DeafenMembers); + /// If True, a user may move other users between voice channels. + public PermValue MoveMembers => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.MoveMembers); + /// If True, a user may use voice activation rather than push-to-talk. + public PermValue UseVoiceActivation => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.UseVoiceActivation); + + /// If True, a user may adjust permissions. + public PermValue ManagePermissions => PermissionsHelper.GetValue(AllowValue, DenyValue, PermissionBits.ManageRolesOrPermissions); + + public ChannelPermissionOverrides(PermValue? createInstantInvite = null, + PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, + PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, + PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, + PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? changeNickname = null, PermValue? manageNicknames = null, + PermValue? managePermissions = null) + : this(new ChannelPermissionOverrides(), createInstantInvite, manageChannel, readMessages, sendMessages, sendTTSMessages, + manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, connect, speak, muteMembers, deafenMembers, + moveMembers, useVoiceActivation, managePermissions) + { + } + public ChannelPermissionOverrides(ChannelPermissionOverrides basePerms, PermValue? createInstantInvite = null, + PermValue? manageChannel = null, PermValue? readMessages = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, + PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, + PermValue? mentionEveryone = null, PermValue? connect = null, PermValue? speak = null, PermValue? muteMembers = null, PermValue? deafenMembers = null, + PermValue? moveMembers = null, PermValue? useVoiceActivation = null, PermValue? changeNickname = null, PermValue? manageNicknames = null, + PermValue? managePermissions = null) + { + uint allow = basePerms.AllowValue, deny = basePerms.DenyValue; + + PermissionsHelper.SetValue(ref allow, ref deny, createInstantInvite, PermissionBits.CreateInstantInvite); + PermissionsHelper.SetValue(ref allow, ref deny, manageChannel, PermissionBits.ManageChannel); + PermissionsHelper.SetValue(ref allow, ref deny, readMessages, PermissionBits.ReadMessages); + PermissionsHelper.SetValue(ref allow, ref deny, sendMessages, PermissionBits.SendMessages); + PermissionsHelper.SetValue(ref allow, ref deny, sendTTSMessages, PermissionBits.SendTTSMessages); + PermissionsHelper.SetValue(ref allow, ref deny, manageMessages, PermissionBits.ManageMessages); + PermissionsHelper.SetValue(ref allow, ref deny, embedLinks, PermissionBits.EmbedLinks); + PermissionsHelper.SetValue(ref allow, ref deny, attachFiles, PermissionBits.AttachFiles); + PermissionsHelper.SetValue(ref allow, ref deny, readMessageHistory, PermissionBits.ReadMessageHistory); + PermissionsHelper.SetValue(ref allow, ref deny, mentionEveryone, PermissionBits.MentionEveryone); + PermissionsHelper.SetValue(ref allow, ref deny, connect, PermissionBits.Connect); + PermissionsHelper.SetValue(ref allow, ref deny, speak, PermissionBits.Speak); + PermissionsHelper.SetValue(ref allow, ref deny, muteMembers, PermissionBits.MuteMembers); + PermissionsHelper.SetValue(ref allow, ref deny, deafenMembers, PermissionBits.DeafenMembers); + PermissionsHelper.SetValue(ref allow, ref deny, moveMembers, PermissionBits.MoveMembers); + PermissionsHelper.SetValue(ref allow, ref deny, useVoiceActivation, PermissionBits.UseVoiceActivation); + PermissionsHelper.SetValue(ref allow, ref deny, changeNickname, PermissionBits.ChangeNickname); + PermissionsHelper.SetValue(ref allow, ref deny, manageNicknames, PermissionBits.ManageNicknames); + PermissionsHelper.SetValue(ref allow, ref deny, managePermissions, PermissionBits.ManageRolesOrPermissions); + + AllowValue = allow; + DenyValue = deny; + } + public ChannelPermissionOverrides(uint allow = 0, uint deny = 0) + { + AllowValue = allow; + DenyValue = deny; + } + } + internal static class PermissionsHelper + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PermValue GetValue(uint allow, uint deny, PermissionBits bit) + { + if (allow.HasBit((byte)bit)) + return PermValue.Allow; + else if (deny.HasBit((byte)bit)) + return PermValue.Deny; + else + return PermValue.Inherit; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool GetValue(uint value, PermissionBits bit) => value.HasBit((byte)bit); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref uint rawValue, bool? value, PermissionBits bit) + { + if (value.HasValue) + { + if (value == true) + SetBit(ref rawValue, bit); + else + UnsetBit(ref rawValue, bit); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetValue(ref uint allow, ref uint deny, PermValue? value, PermissionBits bit) + { + if (value.HasValue) + { + switch (value) + { + case PermValue.Allow: + SetBit(ref allow, bit); + UnsetBit(ref deny, bit); + break; + case PermValue.Deny: + UnsetBit(ref allow, bit); + SetBit(ref deny, bit); + break; + default: + UnsetBit(ref allow, bit); + UnsetBit(ref deny, bit); + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void SetBit(ref uint value, PermissionBits bit) => value |= 1U << (int)bit; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void UnsetBit(ref uint value, PermissionBits bit) => value &= ~(1U << (int)bit); + } +} diff --git a/discord.net/src/Discord.Net/Models/Profile.cs b/discord.net/src/Discord.Net/Models/Profile.cs new file mode 100644 index 000000000..094145d8f --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Profile.cs @@ -0,0 +1,90 @@ +using Discord.API.Client.Rest; +using System; +using System.IO; +using System.Threading.Tasks; +using APIUser = Discord.API.Client.User; + +namespace Discord +{ + public class Profile + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + public DiscordClient Client { get; } + + /// Gets the unique identifier for this user. + public ulong Id { get; } + /// Gets the global name of this user. + public string Name => Client.PrivateUser.Name; + /// Gets the unique identifier for this user's current avatar. + public string AvatarId => Client.PrivateUser.AvatarId; + /// Gets the URL to this user's current avatar. + public string AvatarUrl => Client.PrivateUser.AvatarUrl; + /// Gets an id uniquely identifying from others with the same name. + public ushort Discriminator => Client.PrivateUser.Discriminator; + /// Gets the name of the game this user is currently playing. + public Game? CurrentGame => Client.PrivateUser.CurrentGame; + /// Gets the current status for this user. + public UserStatus Status => Client.PrivateUser.Status; + /// Returns the string used to mention this user. + public string Mention => $"<@{Id}>"; + /// Returns the string used to mention this user by nickname. + public string NicknameMention => $"<@!{Id}>"; + + /// Gets the email for this user. + public string Email { get; private set; } + /// Gets if the email for this user has been verified. + public bool? IsVerified { get; private set; } + + internal Profile(DiscordClient client, ulong id) + { + Client = client; + Id = id; + } + + internal void Update(APIUser model) + { + Email = model.Email; + IsVerified = model.IsVerified; + } + + public async Task Edit(string currentPassword = "", + string username = null, string email = null, string password = null, + Stream avatar = null, ImageType avatarType = ImageType.Png) + { + if (currentPassword == null) throw new ArgumentNullException(nameof(currentPassword)); + + var request = new UpdateProfileRequest() + { + CurrentPassword = currentPassword, + Email = email ?? Email, + Password = password, + Username = username ?? Client.PrivateUser.Name, + AvatarBase64 = avatar.Base64(avatarType, Client.PrivateUser.AvatarId) + }; + + await Client.ClientAPI.Send(request).ConfigureAwait(false); + + if (password != null) + { + var loginRequest = new LoginRequest() + { + Email = Email, + Password = password + }; + var loginResponse = await Client.ClientAPI.Send(loginRequest).ConfigureAwait(false); + Client.ClientAPI.Token = loginResponse.Token; + } + } + + internal Profile Clone() + { + var result = new Profile(); + _cloner(this, result); + return result; + } + private Profile() { } //Used for cloning + + public override string ToString() => Id.ToIdString(); + } +} diff --git a/discord.net/src/Discord.Net/Models/Region.cs b/discord.net/src/Discord.Net/Models/Region.cs new file mode 100644 index 000000000..5b0354048 --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Region.cs @@ -0,0 +1,20 @@ +namespace Discord +{ + public class Region + { + public string Id { get; } + public string Name { get; } + public string Hostname { get; } + public int Port { get; } + public bool Vip { get; } + + internal Region(string id, string name, string hostname, int port, bool vip) + { + Id = id; + Name = name; + Hostname = hostname; + Port = port; + Vip = vip; + } + } +} diff --git a/discord.net/src/Discord.Net/Models/Role.cs b/discord.net/src/Discord.Net/Models/Role.cs new file mode 100644 index 000000000..e19155855 --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Role.cs @@ -0,0 +1,142 @@ +using Discord.API.Client.Rest; +using Discord.Net; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using APIRole = Discord.API.Client.Role; + +namespace Discord +{ + public class Role : IMentionable + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + public DiscordClient Client => Server.Client; + + /// Gets the unique identifier for this role. + public ulong Id { get; } + /// Gets the server this role is a member of. + public Server Server { get; } + + /// Gets the name of this role. + public string Name { get; private set; } + /// If true, this role is displayed isolated from other users. + public bool IsHoisted { get; private set; } + /// Gets the position of this channel relative to other channels in this server. + public int Position { get; private set; } + /// Gets whether this role is managed by server (e.g. for Twitch integration) + public bool IsManaged { get; private set; } + /// Gets whether this role is mentionable by anyone. + public bool IsMentionable { get; private set; } + /// Gets the the permissions given to this role. + public ServerPermissions Permissions { get; private set; } + /// Gets the color of this role. + public Color Color { get; private set; } + + /// Gets the path to this object. + internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; + /// Gets true if this is the role representing all users in a server. + public bool IsEveryone => Id == Server.Id; + /// Gets a list of all members in this role. + public IEnumerable Members => IsEveryone ? Server.Users : Server.Users.Where(x => x.HasRole(this)); + + /// Gets the string used to mention this role. + public string Mention => IsEveryone ? "@everyone" : IsMentionable ? $"<@&{Id}>" : ""; + + internal Role(ulong id, Server server) + { + Id = id; + Server = server; + + Permissions = new ServerPermissions(0); + Color = new Color(0); + } + + internal void Update(APIRole model, bool updatePermissions) + { + if (model.Name != null) + Name = model.Name; + if (model.Hoist != null) + IsHoisted = model.Hoist.Value; + if (model.Managed != null) + IsManaged = model.Managed.Value; + if (model.Mentionable != null) + IsMentionable = model.Mentionable.Value; + if (model.Position != null && !IsEveryone) + Position = model.Position.Value; + if (model.Color != null) + Color = new Color(model.Color.Value); + if (model.Permissions != null) + { + Permissions = new ServerPermissions(model.Permissions.Value); + if (updatePermissions) //Dont update these during READY + { + foreach (var member in Members) + Server.UpdatePermissions(member); + } + } + } + + public async Task Edit(string name = null, ServerPermissions? permissions = null, Color color = null, bool? isHoisted = null, int? position = null, bool? isMentionable = null) + { + var updateRequest = new UpdateRoleRequest(Server.Id, Id) + { + Name = name ?? Name, + Permissions = (permissions ?? Permissions).RawValue, + Color = (color ?? Color).RawValue, + IsHoisted = isHoisted ?? IsHoisted, + IsMentionable = isMentionable ?? IsMentionable + }; + + var updateResponse = await Client.ClientAPI.Send(updateRequest).ConfigureAwait(false); + + if (position != null) + { + int oldPos = Position; + int newPos = position.Value; + int minPos; + Role[] roles = Server.Roles.OrderBy(x => x.Position).ToArray(); + + if (oldPos < newPos) //Moving Down + { + minPos = oldPos; + for (int i = oldPos; i < newPos; i++) + roles[i] = roles[i + 1]; + roles[newPos] = this; + } + else //(oldPos > newPos) Moving Up + { + minPos = newPos; + for (int i = oldPos; i > newPos; i--) + roles[i] = roles[i - 1]; + roles[newPos] = this; + } + + var reorderRequest = new ReorderRolesRequest(Server.Id) + { + RoleIds = roles.Skip(minPos).Select(x => x.Id).ToArray(), + StartPos = minPos + }; + await Client.ClientAPI.Send(reorderRequest).ConfigureAwait(false); + } + } + + public async Task Delete() + { + try { await Client.ClientAPI.Send(new DeleteRoleRequest(Server.Id, Id)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + internal Role Clone() + { + var result = new Role(); + _cloner(this, result); + return result; + } + private Role() { } //Used for cloning + + public override string ToString() => Name ?? Id.ToIdString(); + } +} diff --git a/discord.net/src/Discord.Net/Models/Server.cs b/discord.net/src/Discord.Net/Models/Server.cs new file mode 100644 index 000000000..133c2827a --- /dev/null +++ b/discord.net/src/Discord.Net/Models/Server.cs @@ -0,0 +1,543 @@ +using Discord.API.Client; +using Discord.API.Client.Rest; +using Discord.Net; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; + +namespace Discord +{ + /// Represents a Discord server (also known as a guild). + public class Server + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + internal static string GetIconUrl(ulong serverId, string iconId) + => iconId != null ? $"{DiscordConfig.ClientAPIUrl}guilds/{serverId}/icons/{iconId}.jpg" : null; + internal static string GetSplashUrl(ulong serverId, string splashId) + => splashId != null ? $"{DiscordConfig.ClientAPIUrl}guilds/{serverId}/splashes/{splashId}.jpg" : null; + + public class Emoji + { + public string Id { get; } + + public string Name { get; internal set; } + public bool IsManaged { get; internal set; } + public bool RequireColons { get; internal set; } + public IEnumerable Roles { get; internal set; } + + internal Emoji(string id) + { + Id = id; + } + } + private struct Member + { + public readonly User User; + public readonly ServerPermissions Permissions; + public Member(User user, ServerPermissions permissions) + { + User = user; + Permissions = permissions; + } + } + + private ConcurrentDictionary _roles; + private ConcurrentDictionary _users; + private ConcurrentDictionary _channels; + private ulong _ownerId; + private ulong? _afkChannelId; + private int _userCount; + + public DiscordClient Client { get; } + + /// Gets the unique identifier for this server. + public ulong Id { get; } + + /// Gets the name of this server. + public string Name { get; private set; } + /// Gets the voice region for this server. + public Region Region { get; private set; } + /// Gets the unique identifier for this user's current avatar. + public string IconId { get; private set; } + /// Gets the unique identifier for this server's custom splash image. + public string SplashId { get; private set; } + /// Gets the amount of time (in seconds) a user must be inactive for until they are automatically moved to the AFK voice channel, if one is set. + public int AFKTimeout { get; private set; } + /// Gets the date and time you joined this server. + public DateTime JoinedAt { get; private set; } + /// Gets all extra features added to this server. + public IEnumerable Features { get; private set; } + /// Gets all custom emojis on this server. + public IEnumerable CustomEmojis { get; private set; } + + /// Gets the path to this object. + internal string Path => Name; + /// Gets the user that created this server. + public User Owner => GetUser(_ownerId); + /// Returns true if the current user owns this server. + public bool IsOwner => _ownerId == Client.CurrentUser.Id; + /// Gets the AFK voice channel for this server. + public Channel AFKChannel => _afkChannelId != null ? GetChannel(_afkChannelId.Value) : null; + /// Gets the current user in this server. + public User CurrentUser => GetUser(Client.CurrentUser.Id); + /// Gets the URL to this server's current icon. + public string IconUrl => GetIconUrl(Id, IconId); + /// Gets the URL to this servers's splash image. + public string SplashUrl => GetSplashUrl(Id, SplashId); + /// Gets the default channel for this server. + public Channel DefaultChannel => GetChannel(Id); + /// Gets the the role representing all users in a server. + public Role EveryoneRole => GetRole(Id); + + /// Gets a collection of all channels in this server. + public IEnumerable AllChannels => _channels.Select(x => x.Value); + /// Gets a collection of text channels in this server. + public IEnumerable TextChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Text); + /// Gets a collection of voice channels in this server. + public IEnumerable VoiceChannels => _channels.Select(x => x.Value).Where(x => x.Type == ChannelType.Voice); + /// Gets a collection of all members in this server. + public IEnumerable Users => _users.Select(x => x.Value.User); + /// Gets a collection of all roles in this server. + public IEnumerable Roles => _roles.Select(x => x.Value); + + /// Gets the number of channels in this server. + public int ChannelCount => _channels.Count; + /// Gets the number of users downloaded for this server so far. + internal int CurrentUserCount => _users.Count; + /// Gets the number of users in this server. + public int UserCount => _userCount; + /// Gets the number of roles in this server. + public int RoleCount => _roles.Count; + + internal Server(DiscordClient client, ulong id) + { + Client = client; + Id = id; + } + + internal void Update(Guild model) + { + if (model.Name != null) + Name = model.Name; + if (model.AFKTimeout != null) + AFKTimeout = model.AFKTimeout.Value; + if (model.JoinedAt != null) + JoinedAt = model.JoinedAt.Value; + if (model.OwnerId != null) + _ownerId = model.OwnerId.Value; + if (model.Region != null) + Region = Client.GetRegion(model.Region); + if (model.Icon != null) + IconId = model.Icon; + if (model.Features != null) + Features = model.Features; + if (model.Roles != null) + { + _roles = new ConcurrentDictionary(2, model.Roles.Length); + foreach (var x in model.Roles) + { + var role = AddRole(x.Id); + role.Update(x, false); + } + } + if (model.Emojis != null) //Needs Roles + { + CustomEmojis = model.Emojis.Select(x => new Emoji(x.Id) + { + Name = x.Name, + IsManaged = x.IsManaged, + RequireColons = x.RequireColons, + Roles = x.RoleIds.Select(y => GetRole(y)).Where(y => y != null).ToArray() + }).ToArray(); + } + + //Can be null + _afkChannelId = model.AFKChannelId; + SplashId = model.Splash; + } + internal void Update(ExtendedGuild model) + { + Update(model as Guild); + + //Only channels or members should have AddXXX(cachePerms: true), not both + if (model.Channels != null) + { + _channels = new ConcurrentDictionary(2, (int)(model.Channels.Length * 1.05)); + foreach (var subModel in model.Channels) + AddChannel(subModel.Id, false).Update(subModel); + } + if (model.MemberCount != null) + { + if (_users == null) + _users = new ConcurrentDictionary(2, (int)(model.MemberCount * 1.05)); + _userCount = model.MemberCount.Value; + } + if (!model.IsLarge) + { + if (model.Members != null) + { + foreach (var subModel in model.Members) + AddUser(subModel.User.Id, true, false).Update(subModel); + } + if (model.VoiceStates != null) + { + foreach (var subModel in model.VoiceStates) + GetUser(subModel.UserId)?.Update(subModel); + } + if (model.Presences != null) + { + foreach (var subModel in model.Presences) + GetUser(subModel.User.Id)?.Update(subModel); + } + } + } + + /// Edits this server, changing only non-null attributes. + public Task Edit(string name = null, string region = null, Stream icon = null, ImageType iconType = ImageType.Png) + { + var request = new UpdateGuildRequest(Id) + { + Name = name ?? Name, + Region = region ?? Region.Id, + IconBase64 = icon.Base64(iconType, IconId), + AFKChannelId = AFKChannel?.Id, + AFKTimeout = AFKTimeout, + Splash = SplashId + }; + return Client.ClientAPI.Send(request); + } + + /// Leaves this server. This function will fail if you're the owner - use Delete instead. + public async Task Leave() + { + try { await Client.ClientAPI.Send(new LeaveGuildRequest(Id)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + /// Deletes this server. This function will fail if you're not the owner - use Leave instead. + public async Task Delete() + { + try { await Client.ClientAPI.Send(new DeleteGuildRequest(Id)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + + #region Bans + public async Task> GetBans() + { + var response = await Client.ClientAPI.Send(new GetBansRequest(Id)).ConfigureAwait(false); + return response.Select(x => + { + var user = new User(Client, x.Id, this); + user.Update(x); + return user; + }); + } + + public Task Ban(User user, int pruneDays = 0) + { + var request = new AddGuildBanRequest(user.Server.Id, user.Id) + { + PruneDays = pruneDays + }; + return Client.ClientAPI.Send(request); + } + public Task Unban(User user, int pruneDays = 0) + => Unban(user.Id); + public async Task Unban(ulong userId) + { + try { await Client.ClientAPI.Send(new RemoveGuildBanRequest(Id, userId)).ConfigureAwait(false); } + catch (HttpException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { } + } + #endregion + + #region Channels + internal Channel AddChannel(ulong id, bool cachePerms) + { + var channel = new Channel(Client, id, this); + if (cachePerms && Client.Config.UsePermissionsCache) + { + foreach (var user in Users) + channel.AddUser(user); + } + Client.AddChannel(channel); + return _channels.GetOrAdd(id, x => channel); + } + internal Channel RemoveChannel(ulong id) + { + Channel channel; + _channels.TryRemove(id, out channel); + return channel; + } + + /// Gets the channel with the provided id and owned by this server, or null if not found. + public Channel GetChannel(ulong id) + { + Channel result; + _channels.TryGetValue(id, out result); + return result; + } + + /// Returns all channels with the specified server and name. + /// Name formats supported: Name, #Name and <#Id>. Search is case-insensitive if exactMatch is false. + public IEnumerable FindChannels(string name, ChannelType type = null, bool exactMatch = false) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + return _channels.Select(x => x.Value).Find(name, type, exactMatch); + } + + /// Creates a new channel. + public async Task CreateChannel(string name, ChannelType type) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (type == null) throw new ArgumentNullException(nameof(type)); + + var request = new CreateChannelRequest(Id) { Name = name, Type = type.Value }; + var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); + + var channel = AddChannel(response.Id, true); + channel.Update(response); + return channel; + } + + /// Reorders the provided channels and optionally places them after a certain channel. + public Task ReorderChannels(IEnumerable channels, Channel after = null) + { + if (channels == null) throw new ArgumentNullException(nameof(channels)); + + var request = new ReorderChannelsRequest(Id) + { + ChannelIds = channels.Select(x => x.Id).ToArray(), + StartPos = after != null ? after.Position + 1 : channels.Min(x => x.Position) + }; + return Client.ClientAPI.Send(request); + } + #endregion + + #region Invites + /// Gets all active (non-expired) invites to this server. + public async Task> GetInvites() + { + var response = await Client.ClientAPI.Send(new GetInvitesRequest(Id)).ConfigureAwait(false); + return response.Select(x => + { + var invite = new Invite(Client, x.Code, x.XkcdPass); + invite.Update(x); + return invite; + }); + } + + /// Creates a new invite to the default channel of this server. + /// Time (in seconds) until the invite expires. Set to null to never expire. + /// The max amount of times this invite may be used. Set to null to have unlimited uses. + /// If true, a user accepting this invite will be kicked from the server after closing their client. + /// If true, creates a human-readable link. Not supported if maxAge is set to null. + public Task CreateInvite(int? maxAge = 1800, int? maxUses = null, bool tempMembership = false, bool withXkcd = false) + => DefaultChannel.CreateInvite(maxAge, maxUses, tempMembership, withXkcd); + #endregion + + #region Roles + internal Role AddRole(ulong id) + => _roles.GetOrAdd(id, x => new Role(x, this)); + internal Role RemoveRole(ulong id) + { + Role role; + _roles.TryRemove(id, out role); + return role; + } + + /// Gets the role with the provided id and owned by this server, or null if not found. + public Role GetRole(ulong id) + { + Role result; + _roles.TryGetValue(id, out result); + return result; + } + /// Returns all roles with the specified server and name. + /// Search is case-insensitive if exactMatch is false. + public IEnumerable FindRoles(string name, bool exactMatch = false) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + return _roles.Select(x => x.Value).Find(name, exactMatch); + } + + /// Creates a new role. + public async Task CreateRole(string name, ServerPermissions? permissions = null, Color color = null, bool isHoisted = false, bool isMentionable = false) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + var createRequest = new CreateRoleRequest(Id); + var createResponse = await Client.ClientAPI.Send(createRequest).ConfigureAwait(false); + var role = AddRole(createResponse.Id); + role.Update(createResponse, false); + + var editRequest = new UpdateRoleRequest(role.Server.Id, role.Id) + { + Name = name, + Permissions = (permissions ?? role.Permissions).RawValue, + Color = (color ?? Color.Default).RawValue, + IsHoisted = isHoisted, + IsMentionable = isMentionable + }; + var editResponse = await Client.ClientAPI.Send(editRequest).ConfigureAwait(false); + role.Update(editResponse, true); + + return role; + } + + /// Reorders the provided roles and optionally places them after a certain role. + public Task ReorderRoles(IEnumerable roles, Role after = null) + { + if (roles == null) throw new ArgumentNullException(nameof(roles)); + + return Client.ClientAPI.Send(new ReorderRolesRequest(Id) + { + RoleIds = roles.Select(x => x.Id).ToArray(), + StartPos = after != null ? after.Position + 1 : roles.Min(x => x.Position) + }); + } + #endregion + + #region Permissions + internal ServerPermissions GetPermissions(User user) + { + Member member; + if (_users.TryGetValue(user.Id, out member)) + return member.Permissions; + else + return ServerPermissions.None; + } + + internal void UpdatePermissions(User user) + { + Member member; + if (_users.TryGetValue(user.Id, out member)) + { + var perms = member.Permissions; + if (UpdatePermissions(member.User, ref perms)) + { + _users[user.Id] = new Member(member.User, perms); + foreach (var channel in _channels) + channel.Value.UpdatePermissions(user); + } + } + } + + private bool UpdatePermissions(User user, ref ServerPermissions permissions) + { + uint newPermissions = 0; + + if (user.Id == _ownerId) + newPermissions = ServerPermissions.All.RawValue; + else + { + foreach (var serverRole in user.Roles) + newPermissions |= serverRole.Permissions.RawValue; + } + + if (newPermissions.HasBit((byte)PermissionBits.Administrator)) + newPermissions = ServerPermissions.All.RawValue; + + if (newPermissions != permissions.RawValue) + { + permissions = new ServerPermissions(newPermissions); + return true; + } + return false; + } + #endregion + + #region Users + internal User AddUser(ulong id, bool cachePerms, bool incrementCount) + { + if (incrementCount) + _userCount++; + + Member member; + if (!_users.TryGetValue(id, out member)) //Users can only be added from websocket thread, ignore threadsafety + { + member = new Member(new User(Client, id, this), ServerPermissions.None); + if (id == Client.CurrentUser.Id) + { + member.User.CurrentGame = Client.CurrentGame; + member.User.Status = Client.Status; + } + + _users[id] = member; + if (cachePerms && Client.Config.UsePermissionsCache) + { + foreach (var channel in _channels) + channel.Value.AddUser(member.User); + } + } + return member.User; + } + internal User RemoveUser(ulong id) + { + _userCount--; + Member member; + if (_users.TryRemove(id, out member)) + { + foreach (var channel in _channels) + channel.Value.RemoveUser(id); + return member.User; + } + return null; + } + + /// Gets the user with the provided id and is a member of this server, or null if not found. + public User GetUser(ulong id) + { + Member result; + if (_users.TryGetValue(id, out result)) + return result.User; + else + return null; + } + /// Gets the user with the provided username and discriminator, that is a member of this server, or null if not found. + public User GetUser(string name, ushort discriminator) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + return _users.Select(x => x.Value.User).Find(name, discriminator: discriminator, exactMatch: false).FirstOrDefault(); + } + /// Returns all members of this server with the specified name. + /// Name formats supported: Name, @Name and <@Id>. Search is case-insensitive if exactMatch is false. + public IEnumerable FindUsers(string name, bool exactMatch = false) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + + return _users.Select(x => x.Value.User).Find(name, exactMatch: exactMatch); + } + + /// Kicks all users with an inactivity greater or equal to the provided number of days. + /// If true, no pruning will actually be done but instead return the number of users that would be pruned. + public async Task PruneUsers(int days = 30, bool simulate = false) + { + if (days <= 0) throw new ArgumentOutOfRangeException(nameof(days)); + + var request = new PruneMembersRequest(Id) + { + Days = days, + IsSimulation = simulate + }; + var response = await Client.ClientAPI.Send(request).ConfigureAwait(false); + return response.Pruned; + } + #endregion + + internal Server Clone() + { + var result = new Server(); + _cloner(this, result); + return result; + } + private Server() { } //Used for cloning + + public override string ToString() => Name ?? Id.ToIdString(); + } +} diff --git a/discord.net/src/Discord.Net/Models/User.cs b/discord.net/src/Discord.Net/Models/User.cs new file mode 100644 index 000000000..3a2af6a12 --- /dev/null +++ b/discord.net/src/Discord.Net/Models/User.cs @@ -0,0 +1,402 @@ +using Discord.API.Client; +using Discord.API.Client.Rest; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using APIMember = Discord.API.Client.Member; + +namespace Discord +{ + public class User + { + private readonly static Action _cloner = DynamicIL.CreateCopyMethod(); + + internal static string GetAvatarUrl(ulong userId, string avatarId) + => avatarId != null ? $"{DiscordConfig.ClientAPIUrl}users/{userId}/avatars/{avatarId}.jpg" : null; + + [Flags] + private enum VoiceState : byte + { + None = 0x0, + SelfMuted = 0x01, + SelfDeafened = 0x02, + ServerMuted = 0x04, + ServerDeafened = 0x08, + ServerSuppressed = 0x10, + } + + internal struct CompositeKey : IEquatable + { + public ulong ServerId, UserId; + public CompositeKey(ulong userId, ulong? serverId) + { + ServerId = serverId ?? 0; + UserId = userId; + } + + public bool Equals(CompositeKey other) + => UserId == other.UserId && ServerId == other.ServerId; + public override int GetHashCode() + => unchecked(ServerId.GetHashCode() + UserId.GetHashCode() + 23); + } + + private VoiceState _voiceState; + private DateTime? _lastOnline; + private ulong? _voiceChannelId; + private Dictionary _roles; + + public DiscordClient Client { get; } + + /// Gets the unique identifier for this user. + public ulong Id { get; } + /// Gets the server this user is a member of. + public Server Server { get; } + + /// Gets the name of this user. + public string Name { get; private set; } + /// Gets an id uniquely identifying from others with the same name. + public ushort Discriminator { get; private set; } + /// Gets a user's nickname in a server + public string Nickname { get; internal set; } + /// Gets the unique identifier for this user's current avatar. + public string AvatarId { get; private set; } + /// Gets the name of the game this user is currently playing. + public Game? CurrentGame { get; internal set; } + /// Determines whether this user is a Bot account. + public bool IsBot { get; internal set; } + /// Gets the current status for this user. + public UserStatus Status { get; internal set; } + /// Gets the datetime that this user joined this server. + public DateTime JoinedAt { get; private set; } + /// Returns the time this user last sent/edited a message, started typing or sent voice data in this server. + public DateTime? LastActivityAt { get; private set; } + // /// Gets this user's voice session id. + // public string SessionId { get; private set; } + // /// Gets this user's voice token. + // public string Token { get; private set; } + + /// Gets the path to this object. + internal string Path => $"{Server?.Name ?? "[Private]"}/{Name}"; + /// Gets the current private channel for this user if one exists. + public Channel PrivateChannel => Client.GetPrivateChannel(Id); + /// Returns the string used to mention this user. + public string Mention => $"<@{Id}>"; + /// Returns the string used to mention this user by nickname. + public string NicknameMention => $"<@!{Id}>"; + /// Returns true if this user has marked themselves as muted. + public bool IsSelfMuted => (_voiceState & VoiceState.SelfMuted) != 0; + /// Returns true if this user has marked themselves as deafened. + public bool IsSelfDeafened => (_voiceState & VoiceState.SelfDeafened) != 0; + /// Returns true if the server is blocking audio from this user. + public bool IsServerMuted => (_voiceState & VoiceState.ServerMuted) != 0; + /// Returns true if the server is blocking audio to this user. + public bool IsServerDeafened => (_voiceState & VoiceState.ServerDeafened) != 0; + /// Returns true if the server is temporarily blocking audio to/from this user. + public bool IsServerSuppressed => (_voiceState & VoiceState.ServerSuppressed) != 0; + /// Returns the time this user was last seen online in this server. + public DateTime? LastOnlineAt => Status != UserStatus.Offline ? DateTime.UtcNow : _lastOnline; + /// Gets this user's current voice channel. + public Channel VoiceChannel => _voiceChannelId != null ? Server.GetChannel(_voiceChannelId.Value) : null; + /// Gets the URL to this user's current avatar. + public string AvatarUrl => GetAvatarUrl(Id, AvatarId); + /// Gets all roles that have been assigned to this user, including the everyone role. + public IEnumerable Roles => _roles.Select(x => x.Value); + + /// Returns a collection of all channels this user has permissions to join on this server. + public IEnumerable Channels + { + get + { + if (Server != null) + { + if (Client.Config.UsePermissionsCache) + { + return Server.AllChannels.Where(x => + (x.Type == ChannelType.Text && x.GetPermissions(this).ReadMessages) || + (x.Type == ChannelType.Voice && x.GetPermissions(this).Connect)); + } + else + { + ChannelPermissions perms = new ChannelPermissions(); + return Server.AllChannels + .Where(x => + { + x.UpdatePermissions(this, ref perms); + return (x.Type == ChannelType.Text && perms.ReadMessages) || + (x.Type == ChannelType.Voice && perms.Connect); + }); + } + } + else + { + if (this == Client.PrivateUser) + return Client.PrivateChannels; + else + { + var privateChannel = Client.GetPrivateChannel(Id); + if (privateChannel != null) + return new Channel[] { privateChannel }; + else + return new Channel[0]; + } + } + } + } + + internal User(DiscordClient client, ulong id, Server server) + { + Client = client; + Id = id; + Server = server; + + _roles = new Dictionary(); + Status = UserStatus.Offline; + + if (server == null) + UpdateRoles(null); + } + + internal void Update(UserReference model) + { + if (model.Username != null) + Name = model.Username; + if (model.Discriminator != null) + Discriminator = model.Discriminator.Value; + if (model.Avatar != null) + AvatarId = model.Avatar; + if (model.Bot != null) + IsBot = model.Bot.Value; + + } + internal void Update(APIMember model) + { + if (model.User != null) + Update(model.User); + + if (model.JoinedAt.HasValue) + JoinedAt = model.JoinedAt.Value; + if (model.Roles != null) + UpdateRoles(model.Roles.Select(x => Server.GetRole(x))); + if (model.Nick != "") + Nickname = model.Nick; + } + internal void Update(ExtendedMember model) + { + Update(model as APIMember); + + if (model.IsServerMuted == true) + _voiceState |= VoiceState.ServerMuted; + else if (model.IsServerMuted == false) + _voiceState &= ~VoiceState.ServerMuted; + + if (model.IsServerDeafened == true) + _voiceState |= VoiceState.ServerDeafened; + else if (model.IsServerDeafened == false) + _voiceState &= ~VoiceState.ServerDeafened; + } + internal void Update(MemberPresence model) + { + if (model.User != null) + Update(model.User as UserReference); + + if (model.Roles != null) + UpdateRoles(model.Roles.Select(x => Server.GetRole(x))); + if (model.Status != null && Status != model.Status) + { + Status = UserStatus.FromString(model.Status); + if (Status == UserStatus.Offline) + _lastOnline = DateTime.UtcNow; + } + + if (model.Game != null) + CurrentGame = new Game(model.Game.Name, model.Game.Type, model.Game.Url); + else + CurrentGame = null; + } + internal void Update(MemberVoiceState model) + { + if (model.IsSelfMuted == true) + _voiceState |= VoiceState.SelfMuted; + else if (model.IsSelfMuted == false) + _voiceState &= ~VoiceState.SelfMuted; + if (model.IsSelfDeafened == true) + _voiceState |= VoiceState.SelfDeafened; + else if (model.IsSelfDeafened == false) + _voiceState &= ~VoiceState.SelfDeafened; + if (model.IsServerMuted == true) + _voiceState |= VoiceState.ServerMuted; + else if (model.IsServerMuted == false) + _voiceState &= ~VoiceState.ServerMuted; + if (model.IsServerDeafened == true) + _voiceState |= VoiceState.ServerDeafened; + else if (model.IsServerDeafened == false) + _voiceState &= ~VoiceState.ServerDeafened; + if (model.IsServerSuppressed == true) + _voiceState |= VoiceState.ServerSuppressed; + else if (model.IsServerSuppressed == false) + _voiceState &= ~VoiceState.ServerSuppressed; + + /*if (model.SessionId != null) + SessionId = model.SessionId; + if (model.Token != null) + Token = model.Token;*/ + + _voiceChannelId = model.ChannelId; //Allows null + } + + internal void UpdateActivity(DateTime? activity = null) + { + if (LastActivityAt == null || activity > LastActivityAt.Value) + LastActivityAt = activity ?? DateTime.UtcNow; + } + + public async Task Edit(bool? isMuted = null, bool? isDeafened = null, Channel voiceChannel = null, IEnumerable roles = null, string nickname = "") + { + if (Server == null) throw new InvalidOperationException("Unable to edit users in a private channel"); + + //Modify the roles collection and filter out the everyone role + var roleIds = (roles ?? Roles) + .Where(x => !x.IsEveryone) + .Select(x => x.Id) + .Distinct() + .ToArray(); + + bool isCurrentUser = Id == Server.CurrentUser.Id; + if (isCurrentUser && nickname != "") + { + var request = new UpdateOwnNick(Server.Id, nickname ?? ""); + await Client.ClientAPI.Send(request).ConfigureAwait(false); + nickname = ""; + } + if (!isCurrentUser || isMuted != null || isDeafened != null | voiceChannel != null || roles != null) + { + if (nickname == "") nickname = Nickname; + var request = new UpdateMemberRequest(Server.Id, Id) + { + IsMuted = isMuted ?? IsServerMuted, + IsDeafened = isDeafened ?? IsServerDeafened, + VoiceChannelId = voiceChannel?.Id, + RoleIds = roleIds, + Nickname = nickname ?? "" + }; + await Client.ClientAPI.Send(request).ConfigureAwait(false); + } + } + + public Task Kick() + { + if (Server == null) throw new InvalidOperationException("Unable to kick users from a private channel"); + + var request = new KickMemberRequest(Server.Id, Id); + return Client.ClientAPI.Send(request); + } + + #region Permissions + public ServerPermissions ServerPermissions + { + get + { + if (Server == null) throw new InvalidOperationException("Unable to get server permissions from a private channel"); + + return Server.GetPermissions(this); + } + } + + public ChannelPermissions GetPermissions(Channel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return channel.GetPermissions(this); + } + #endregion + + #region Channels + public Task CreatePMChannel() + => Client.CreatePMChannel(this); + #endregion + + #region Messages + public async Task SendMessage(string text) + { + if (text == null) throw new ArgumentNullException(nameof(text)); + + var channel = await CreatePMChannel().ConfigureAwait(false); + return await channel.SendMessage(text).ConfigureAwait(false); + } + public async Task SendFile(string filePath) + { + if (filePath == null) throw new ArgumentNullException(nameof(filePath)); + + var channel = await CreatePMChannel().ConfigureAwait(false); + return await channel.SendFile(filePath).ConfigureAwait(false); + } + public async Task SendFile(string filename, Stream stream) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + if (stream == null) throw new ArgumentNullException(nameof(stream)); + + var channel = await CreatePMChannel().ConfigureAwait(false); + return await channel.SendFile(filename, stream).ConfigureAwait(false); + } + #endregion + + #region Roles + private void UpdateRoles(IEnumerable roles) + { + bool updated = false; + var newRoles = new Dictionary(); + + var oldRoles = _roles; + if (roles != null) + { + foreach (var r in roles) + { + if (r != null) + { + newRoles[r.Id] = r; + if (!oldRoles.ContainsKey(r.Id)) + updated = true; //Check for adds + } + } + } + + if (Server != null) + { + var everyone = Server.EveryoneRole; + newRoles[everyone.Id] = everyone; + } + if (oldRoles.Count != newRoles.Count) + updated = true; //Check for removes + + if (updated) + { + _roles = newRoles; + if (Server != null) + Server.UpdatePermissions(this); + } + } + public bool HasRole(Role role) + { + if (role == null) throw new ArgumentNullException(nameof(role)); + + return _roles.ContainsKey(role.Id); + } + + public Task AddRoles(params Role[] roles) + => Edit(roles: Roles.Concat(roles)); + public Task RemoveRoles(params Role[] roles) + => Edit(roles: Roles.Except(roles)); + #endregion + + internal User Clone() + { + var result = new User(); + _cloner(this, result); + return result; + } + private User() { } //Used for cloning + + public override string ToString() => Name != null ? $"{Name}#{Discriminator}" : Id.ToIdString(); + } +} \ No newline at end of file diff --git a/discord.net/src/Discord.Net/Net/HttpException.cs b/discord.net/src/Discord.Net/Net/HttpException.cs new file mode 100644 index 000000000..306122ba3 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/HttpException.cs @@ -0,0 +1,23 @@ +using System; +using System.Net; + +namespace Discord.Net +{ +#if NET46 + [Serializable] +#endif + public class HttpException : Exception + { + public HttpStatusCode StatusCode { get; } + + public HttpException(HttpStatusCode statusCode) + : base($"The server responded with error {(int)statusCode} ({statusCode})") + { + StatusCode = statusCode; + } +#if NET46 + public override void GetObjectData(SerializationInfo info, StreamingContext context) + => base.GetObjectData(info, context); +#endif + } +} diff --git a/discord.net/src/Discord.Net/Net/Rest/BuiltInEngine.cs b/discord.net/src/Discord.Net/Net/Rest/BuiltInEngine.cs new file mode 100644 index 000000000..71a4d09d6 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/Rest/BuiltInEngine.cs @@ -0,0 +1,145 @@ +#if NETSTANDARD1_3 +using Discord.Logging; +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Net.Http; +using System.Net; +using System.Text; +using System.Globalization; +using Nito.AsyncEx; + +namespace Discord.Net.Rest +{ + internal class BuiltInEngine : IRestEngine + { + private const int HR_SECURECHANNELFAILED = -2146233079; + + private readonly DiscordConfig _config; + private readonly HttpClient _client; + private readonly string _baseUrl; + + private readonly AsyncLock _rateLimitLock; + private readonly ILogger _logger; + private DateTime _rateLimitTime; + + + public BuiltInEngine(DiscordConfig config, string baseUrl, ILogger logger) + { + _config = config; + _baseUrl = baseUrl; + _logger = logger; + + _rateLimitLock = new AsyncLock(); + _client = new HttpClient(new HttpClientHandler + { + AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, + UseCookies = false, + UseProxy = false, + PreAuthenticate = false //We do auth ourselves + }); + _client.DefaultRequestHeaders.Add("accept", "*/*"); + _client.DefaultRequestHeaders.Add("accept-encoding", "gzip,deflate"); + _client.DefaultRequestHeaders.Add("user-agent", config.UserAgent); + } + + public void SetToken(string token) + { + _client.DefaultRequestHeaders.Remove("authorization"); + if (token != null) + _client.DefaultRequestHeaders.Add("authorization", token); + } + + public async Task Send(string method, string path, string json, CancellationToken cancelToken) + { + using (var request = new HttpRequestMessage(GetMethod(method), _baseUrl + path)) + { + if (json != null) + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + return await Send(request, cancelToken).ConfigureAwait(false); + } + } + public async Task SendFile(string method, string path, string filename, Stream stream, CancellationToken cancelToken) + { + using (var request = new HttpRequestMessage(GetMethod(method), _baseUrl + path)) + { + var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); + content.Add(new StreamContent(File.OpenRead(path)), "file", filename); + request.Content = content; + return await Send(request, cancelToken).ConfigureAwait(false); + } + } + private async Task Send(HttpRequestMessage request, CancellationToken cancelToken) + { + int retryCount = 0; + while (true) + { + HttpResponseMessage response; + try + { + response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); + } + catch (WebException ex) + { + //The request was aborted: Could not create SSL/TLS secure channel. + if (ex.HResult == HR_SECURECHANNELFAILED && retryCount++ < 5) + continue; //Retrying seems to fix this somehow? + throw; + } + + int statusCode = (int)response.StatusCode; + if (statusCode == 429) //Rate limit + { + var retryAfter = response.Headers + .Where(x => x.Key.Equals("Retry-After", StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Value.FirstOrDefault()) + .FirstOrDefault(); + + int milliseconds; + if (retryAfter != null && int.TryParse(retryAfter, out milliseconds)) + { + if (_logger != null) + { + var now = DateTime.UtcNow; + if (now >= _rateLimitTime) + { + using (await _rateLimitLock.LockAsync().ConfigureAwait(false)) + { + if (now >= _rateLimitTime) + { + _rateLimitTime = now.AddMilliseconds(milliseconds); + _logger.Warning($"Rate limit hit, waiting {Math.Round(milliseconds / 1000.0f, 2)} seconds"); + } + } + } + } + await Task.Delay(milliseconds, cancelToken).ConfigureAwait(false); + continue; + } + throw new HttpException(response.StatusCode); + } + else if (statusCode < 200 || statusCode >= 300) //2xx = Success + throw new HttpException(response.StatusCode); + else + return await response.Content.ReadAsStringAsync().ConfigureAwait(false); + } + } + + private static readonly HttpMethod _patch = new HttpMethod("PATCH"); + private HttpMethod GetMethod(string method) + { + switch (method) + { + case "DELETE": return HttpMethod.Delete; + case "GET": return HttpMethod.Get; + case "PATCH": return _patch; + case "POST": return HttpMethod.Post; + case "PUT": return HttpMethod.Put; + default: throw new InvalidOperationException($"Unknown HttpMethod: {method}"); + } + } + } +} +#endif \ No newline at end of file diff --git a/discord.net/src/Discord.Net/Net/Rest/CompletedRequestEventArgs.cs b/discord.net/src/Discord.Net/Net/Rest/CompletedRequestEventArgs.cs new file mode 100644 index 000000000..1bee431b0 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/Rest/CompletedRequestEventArgs.cs @@ -0,0 +1,19 @@ +using Discord.API; + +namespace Discord.Net.Rest +{ + public class CompletedRequestEventArgs : RequestEventArgs + { + public object Response { get; set; } + public string ResponseJson { get; set; } + public double Milliseconds { get; set; } + + public CompletedRequestEventArgs(IRestRequest request, object response, string responseJson, double milliseconds) + : base(request) + { + Response = response; + ResponseJson = responseJson; + Milliseconds = milliseconds; + } + } +} diff --git a/discord.net/src/Discord.Net/Net/Rest/IRestEngine.cs b/discord.net/src/Discord.Net/Net/Rest/IRestEngine.cs new file mode 100644 index 000000000..faba37086 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/Rest/IRestEngine.cs @@ -0,0 +1,13 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.Rest +{ + internal interface IRestEngine + { + void SetToken(string token); + Task Send(string method, string path, string json, CancellationToken cancelToken); + Task SendFile(string method, string path, string filename, Stream stream, CancellationToken cancelToken); + } +} diff --git a/discord.net/src/Discord.Net/Net/Rest/JsonRestClient.cs b/discord.net/src/Discord.Net/Net/Rest/JsonRestClient.cs new file mode 100644 index 000000000..ac18ac823 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/Rest/JsonRestClient.cs @@ -0,0 +1,42 @@ +using Discord.Logging; +using Newtonsoft.Json; +#if TEST_RESPONSES +using System; +#endif +using System.IO; + +namespace Discord.Net.Rest +{ + public class JsonRestClient : RestClient + { + private JsonSerializer _serializer; + + public JsonRestClient(DiscordConfig config, string baseUrl, ILogger logger = null) + : base(config, baseUrl, logger) + { + _serializer = new JsonSerializer(); +#if TEST_RESPONSES + _serializer.CheckAdditionalContent = true; + _serializer.MissingMemberHandling = MissingMemberHandling.Error; +#else + _serializer.CheckAdditionalContent = false; + _serializer.MissingMemberHandling = MissingMemberHandling.Ignore; +#endif + } + + protected override string Serialize(T obj) + { + return JsonConvert.SerializeObject(obj); + } + + protected override T Deserialize(string json) + { +#if TEST_RESPONSES + if (string.IsNullOrEmpty(json)) + throw new Exception("API check failed: Response is empty."); +#endif + using (var reader = new JsonTextReader(new StringReader(json))) + return (T)_serializer.Deserialize(reader, typeof(T)); + } + } +} diff --git a/discord.net/src/Discord.Net/Net/Rest/RequestEventArgs.cs b/discord.net/src/Discord.Net/Net/Rest/RequestEventArgs.cs new file mode 100644 index 000000000..ce6dadd83 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/Rest/RequestEventArgs.cs @@ -0,0 +1,16 @@ +using Discord.API; +using System; + +namespace Discord.Net.Rest +{ + public class RequestEventArgs : EventArgs + { + public IRestRequest Request { get; set; } + public bool Cancel { get; set; } + + public RequestEventArgs(IRestRequest request) + { + Request = request; + } + } +} diff --git a/discord.net/src/Discord.Net/Net/Rest/RestClient.cs b/discord.net/src/Discord.Net/Net/Rest/RestClient.cs new file mode 100644 index 000000000..f5e1a6471 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/Rest/RestClient.cs @@ -0,0 +1,139 @@ +using Discord.API; +using Discord.ETF; +using Discord.Logging; +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.Rest +{ + public abstract partial class RestClient + { + private struct RestResults + { + public string Response { get; set; } + public double Milliseconds { get; set; } + + public RestResults(string response, double milliseconds) + { + Response = response; + Milliseconds = milliseconds; + } + } + + public event EventHandler SendingRequest = delegate { }; + public event EventHandler SentRequest = delegate { }; + + private bool OnSendingRequest(IRestRequest request) + { + var eventArgs = new RequestEventArgs(request); + SendingRequest(this, eventArgs); + return !eventArgs.Cancel; + } + private void OnSentRequest(IRestRequest request, object response, string responseJson, double milliseconds) + => SentRequest(this, new CompletedRequestEventArgs(request, response, responseJson, milliseconds)); + + private readonly DiscordConfig _config; + private readonly IRestEngine _engine; + private readonly ILogger _logger; + private string _token; + + public CancellationToken CancelToken { get; set; } + + public string Token + { + get { return _token; } + set + { + _token = value; + _engine.SetToken(value); + } + } + + protected RestClient(DiscordConfig config, string baseUrl, ILogger logger = null) + { + _config = config; + _logger = logger; + +#if !NETSTANDARD1_3 + _engine = new RestSharpEngine(config, baseUrl, logger); +#else + _engine = new BuiltInEngine(config, baseUrl, logger); +#endif + + if (logger != null && logger.Level >= LogSeverity.Verbose) + SentRequest += (s, e) => _logger.Verbose($"{e.Request.Method} {e.Request.Endpoint}: {e.Milliseconds} ms"); + } + + public async Task Send(IRestRequest request) + where ResponseT : class + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + if (!OnSendingRequest(request)) throw new OperationCanceledException(); + var results = await Send(request, true).ConfigureAwait(false); + var response = Deserialize(results.Response); + OnSentRequest(request, response, results.Response, results.Milliseconds); + + return response; + } + public async Task Send(IRestRequest request) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + if (!OnSendingRequest(request)) throw new OperationCanceledException(); + var results = await Send(request, false).ConfigureAwait(false); + OnSentRequest(request, null, null, results.Milliseconds); + } + + public async Task Send(IRestFileRequest request) + where ResponseT : class + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + if (!OnSendingRequest(request)) throw new OperationCanceledException(); + var results = await SendFile(request, true).ConfigureAwait(false); + var response = Deserialize(results.Response); + OnSentRequest(request, response, results.Response, results.Milliseconds); + + return response; + } + public async Task Send(IRestFileRequest request) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + if (!OnSendingRequest(request)) throw new OperationCanceledException(); + var results = await SendFile(request, false).ConfigureAwait(false); + OnSentRequest(request, null, null, results.Milliseconds); + } + + private async Task Send(IRestRequest request, bool hasResponse) + { + object payload = request.Payload; + string requestJson = null; + if (payload != null) + requestJson = Serialize(payload); + + Stopwatch stopwatch = Stopwatch.StartNew(); + string responseJson = await _engine.Send(request.Method, request.Endpoint, requestJson, CancelToken).ConfigureAwait(false); + stopwatch.Stop(); + + double milliseconds = Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); + return new RestResults(responseJson, milliseconds); + } + + private async Task SendFile(IRestFileRequest request, bool hasResponse) + { + Stopwatch stopwatch = Stopwatch.StartNew(); + string responseJson = await _engine.SendFile(request.Method, request.Endpoint, request.Filename, request.Stream, CancelToken).ConfigureAwait(false); + stopwatch.Stop(); + + double milliseconds = Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); + return new RestResults(responseJson, milliseconds); + } + + protected abstract string Serialize(T obj); + protected abstract T Deserialize(string json); + } +} diff --git a/discord.net/src/Discord.Net/Net/Rest/SharpRestEngine.cs b/discord.net/src/Discord.Net/Net/Rest/SharpRestEngine.cs new file mode 100644 index 000000000..dda6e40e2 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/Rest/SharpRestEngine.cs @@ -0,0 +1,132 @@ +#if !NETSTANDARD1_3 +using Discord.Logging; +using Nito.AsyncEx; +using RestSharp; +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using RestSharpClient = RestSharp.RestClient; + +namespace Discord.Net.Rest +{ + internal class RestSharpEngine : IRestEngine + { + private const int HR_SECURECHANNELFAILED = -2146233079; + + private readonly DiscordConfig _config; + private readonly RestSharpClient _client; + + private readonly AsyncLock _rateLimitLock; + private readonly ILogger _logger; + private DateTime _rateLimitTime; + + public RestSharpEngine(DiscordConfig config, string baseUrl, ILogger logger) + { + _config = config; + _logger = logger; + + _rateLimitLock = new AsyncLock(); + _client = new RestSharpClient(baseUrl) + { + PreAuthenticate = false, + ReadWriteTimeout = DiscordConfig.RestTimeout, + UserAgent = config.UserAgent + }; + _client.Proxy = null; + _client.RemoveDefaultParameter("Accept"); + _client.AddDefaultHeader("accept", "*/*"); + _client.AddDefaultHeader("accept-encoding", "gzip,deflate"); + } + + public void SetToken(string token) + { + _client.RemoveDefaultParameter("authorization"); + if (token != null) + _client.AddDefaultHeader("authorization", token); + } + + public Task Send(string method, string path, string json, CancellationToken cancelToken) + { + var request = new RestRequest(path, GetMethod(method)); + if (json != null) + request.AddParameter("application/json", json, ParameterType.RequestBody); + return Send(request, cancelToken); + } + public Task SendFile(string method, string path, string filename, Stream stream, CancellationToken cancelToken) + { + var request = new RestRequest(path, GetMethod(method)); + request.AddHeader("content-length", (stream.Length - stream.Position).ToString()); + + byte[] bytes = new byte[stream.Length - stream.Position]; + stream.Read(bytes, 0, bytes.Length); + request.AddFileBytes("file", bytes, filename); + //request.AddFile("file", x => stream.CopyTo(x), filename); (Broken in latest ver) + + return Send(request, cancelToken); + } + private async Task Send(RestRequest request, CancellationToken cancelToken) + { + int retryCount = 0; + while (true) + { + var response = await _client.ExecuteTaskAsync(request, cancelToken).ConfigureAwait(false); + int statusCode = (int)response.StatusCode; + if (statusCode == 0) //Internal Error + { + //The request was aborted: Could not create SSL/TLS secure channel. + if (response.ErrorException.HResult == HR_SECURECHANNELFAILED && retryCount++ < 5) + continue; //Retrying seems to fix this somehow? + throw response.ErrorException; + } + else if (statusCode == 429) //Rate limit + { + var retryAfter = response.Headers + .FirstOrDefault(x => x.Name.Equals("Retry-After", StringComparison.OrdinalIgnoreCase)); + + int milliseconds; + if (retryAfter != null && int.TryParse((string)retryAfter.Value, out milliseconds)) + { + if (_logger != null) + { + var now = DateTime.UtcNow; + if (now >= _rateLimitTime) + { + using (await _rateLimitLock.LockAsync().ConfigureAwait(false)) + { + if (now >= _rateLimitTime) + { + _rateLimitTime = now.AddMilliseconds(milliseconds); + _logger.Warning($"Rate limit hit, waiting {Math.Round(milliseconds / 1000.0f, 2)} seconds"); + } + } + } + } + await Task.Delay(milliseconds, cancelToken).ConfigureAwait(false); + continue; + } + throw new HttpException(response.StatusCode); + } + else if (statusCode < 200 || statusCode >= 300) //2xx = Success + throw new HttpException(response.StatusCode); + else + return response.Content; + } + } + + private Method GetMethod(string method) + { + switch (method) + { + case "DELETE": return Method.DELETE; + case "GET": return Method.GET; + case "PATCH": return Method.PATCH; + case "POST": return Method.POST; + case "PUT": return Method.PUT; + default: throw new InvalidOperationException($"Unknown HttpMethod: {method}"); + } + } + } +} +#endif \ No newline at end of file diff --git a/discord.net/src/Discord.Net/Net/TimeoutException.cs b/discord.net/src/Discord.Net/Net/TimeoutException.cs new file mode 100644 index 000000000..051eeb263 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/TimeoutException.cs @@ -0,0 +1,15 @@ +using System; + +namespace Discord.Net +{ +#if NET46 + [Serializable] +#endif + public class TimeoutException : OperationCanceledException + { + public TimeoutException() + : base("An operation has timed out.") + { + } + } +} diff --git a/discord.net/src/Discord.Net/Net/WebSocketException.cs b/discord.net/src/Discord.Net/Net/WebSocketException.cs new file mode 100644 index 000000000..b845d90c4 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSocketException.cs @@ -0,0 +1,25 @@ +using System; + +namespace Discord.Net +{ + public class WebSocketException : Exception + { + public int Code { get; } + public string Reason { get; } + + public WebSocketException(int code, string reason) + : base(GenerateMessage(code, reason)) + { + Code = code; + Reason = reason; + } + + private static string GenerateMessage(int? code, string reason) + { + if (!String.IsNullOrEmpty(reason)) + return $"Received close code {code}: {reason}"; + else + return $"Received close code {code}"; + } + } +} diff --git a/discord.net/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs b/discord.net/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs new file mode 100644 index 000000000..7c6f633e9 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSockets/BinaryMessageEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord.Net.WebSockets +{ + public class BinaryMessageEventArgs : EventArgs + { + public byte[] Data { get; } + + public BinaryMessageEventArgs(byte[] data) { Data = data; } + } +} diff --git a/discord.net/src/Discord.Net/Net/WebSockets/BuiltInEngine.cs b/discord.net/src/Discord.Net/Net/WebSockets/BuiltInEngine.cs new file mode 100644 index 000000000..460e608eb --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSockets/BuiltInEngine.cs @@ -0,0 +1,158 @@ +#if NETSTANDARD1_3 +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using WebSocketClient = System.Net.WebSockets.ClientWebSocket; + +namespace Discord.Net.WebSockets +{ + internal class BuiltInEngine : IWebSocketEngine + { + private const int ReceiveChunkSize = 12 * 1024; //12KB + private const int SendChunkSize = 4 * 1024; //4KB + private const int HR_TIMEOUT = -2147012894; + + private readonly DiscordConfig _config; + private readonly ConcurrentQueue _sendQueue; + private WebSocketClient _webSocket; + private Task _tempTask; + + public event EventHandler BinaryMessage = delegate { }; + public event EventHandler TextMessage = delegate { }; + private void OnBinaryMessage(byte[] data) + => BinaryMessage(this, new BinaryMessageEventArgs(data)); + private void OnTextMessage(string msg) + => TextMessage(this, new TextMessageEventArgs(msg)); + + internal BuiltInEngine(DiscordConfig config) + { + _config = config; + _sendQueue = new ConcurrentQueue(); + } + + public async Task Connect(string host, CancellationToken cancelToken) + { + _webSocket = new WebSocketClient(); + _webSocket.Options.Proxy = null; + _webSocket.Options.SetRequestHeader("User-Agent", _config.UserAgent); + _webSocket.Options.KeepAliveInterval = TimeSpan.Zero; + _tempTask = await _webSocket.ConnectAsync(new Uri(host), cancelToken)//.ConfigureAwait(false); + .ContinueWith(t => ReceiveAsync(cancelToken)).ConfigureAwait(false); + //TODO: ContinueWith is a temporary hack, may be a bug related to https://github.com/dotnet/corefx/issues/4429 + } + + public Task Disconnect() + { + string ignored; + while (_sendQueue.TryDequeue(out ignored)) { } + + var socket = _webSocket; + _webSocket = null; + + return TaskHelper.CompletedTask; + } + + public IEnumerable GetTasks(CancellationToken cancelToken) + => new Task[] { /*ReceiveAsync(cancelToken),*/ _tempTask, SendAsync(cancelToken) }; + + private Task ReceiveAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + var buffer = new ArraySegment(new byte[ReceiveChunkSize]); + var stream = new MemoryStream(); + + try + { + while (!cancelToken.IsCancellationRequested) + { + WebSocketReceiveResult result = null; + do + { + if (cancelToken.IsCancellationRequested) return; + + try + { + result = await _webSocket.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); + } + catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) + { + throw new Exception($"Connection timed out."); + } + + if (result.MessageType == WebSocketMessageType.Close) + throw new WebSocketException((int)result.CloseStatus.Value, result.CloseStatusDescription); + else + stream.Write(buffer.Array, 0, result.Count); + + } + while (result == null || !result.EndOfMessage); + + var array = stream.ToArray(); + if (result.MessageType == WebSocketMessageType.Binary) + OnBinaryMessage(array); + else if (result.MessageType == WebSocketMessageType.Text) + OnTextMessage(Encoding.UTF8.GetString(array, 0, array.Length)); + + stream.Position = 0; + stream.SetLength(0); + } + } + catch (OperationCanceledException) { } + }); + } + private Task SendAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + byte[] bytes = new byte[SendChunkSize]; + + try + { + while (!cancelToken.IsCancellationRequested) + { + string json; + while (_sendQueue.TryDequeue(out json)) + { + int byteCount = Encoding.UTF8.GetBytes(json, 0, json.Length, bytes, 0); + int frameCount = (int)Math.Ceiling((double)byteCount / SendChunkSize); + + int offset = 0; + for (var i = 0; i < frameCount; i++, offset += SendChunkSize) + { + bool isLast = i == (frameCount - 1); + + int count; + if (isLast) + count = byteCount - (i * SendChunkSize); + else + count = SendChunkSize; + + try + { + await _webSocket.SendAsync(new ArraySegment(bytes, offset, count), WebSocketMessageType.Text, isLast, cancelToken).ConfigureAwait(false); + } + catch (Win32Exception ex) when (ex.HResult == HR_TIMEOUT) + { + return; + } + } + } + await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + + public void QueueMessage(string message) + => _sendQueue.Enqueue(message); + } +} +#endif \ No newline at end of file diff --git a/discord.net/src/Discord.Net/Net/WebSockets/GatewaySocket.cs b/discord.net/src/Discord.Net/Net/WebSockets/GatewaySocket.cs new file mode 100644 index 000000000..33f890878 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSockets/GatewaySocket.cs @@ -0,0 +1,186 @@ +using Discord.API.Client; +using Discord.API.Client.GatewaySocket; +using Discord.API.Client.Rest; +using Discord.Logging; +using Discord.Net.Rest; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using APIGame = Discord.API.Client.Game; + +namespace Discord.Net.WebSockets +{ + public class GatewaySocket : WebSocket + { + private RestClient _rest; + private uint _lastSequence; + private int _reconnects; + + //public string Token { get; private set; } + public string SessionId { get; private set; } + + public event EventHandler ReceivedDispatch = delegate { }; + private void OnReceivedDispatch(string type, JToken payload) + => ReceivedDispatch(this, new WebSocketEventEventArgs(type, payload)); + + public GatewaySocket(DiscordConfig config, JsonSerializer serializer, Logger logger) + : base(config, serializer, logger) + { + Disconnected += async (s, e) => + { + if (e.WasUnexpected) + await Reconnect().ConfigureAwait(false); + }; + } + + public async Task Connect(RestClient rest, CancellationToken parentCancelToken) + { + _rest = rest; + //Token = rest.Token; + + var gatewayResponse = await rest.Send(new GatewayRequest()).ConfigureAwait(false); + string url = $"{gatewayResponse.Url}/?encoding=json&v=4"; + Logger.Verbose($"Login successful, gateway: {url}"); + + Host = url; + await BeginConnect(parentCancelToken).ConfigureAwait(false); + if (SessionId == null) + SendIdentify(_rest.Token); + else + SendResume(); + } + private async Task Reconnect() + { + try + { + var cancelToken = _parentCancelToken; + if (_reconnects++ == 0) + await Task.Delay(_config.ReconnectDelay, cancelToken).ConfigureAwait(false); + else + await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); + + while (!cancelToken.IsCancellationRequested) + { + try + { + await Connect(_rest, _parentCancelToken).ConfigureAwait(false); + break; + } + catch (OperationCanceledException) { throw; } + catch (Exception ex) + { + Logger.Error("Reconnect failed", ex); + //Net is down? We can keep trying to reconnect until the user runs Disconnect() + await Task.Delay(_config.FailedReconnectDelay, cancelToken).ConfigureAwait(false); + } + } + } + catch (OperationCanceledException) { } + } + public async Task Disconnect() + { + await _taskManager.Stop(true).ConfigureAwait(false); + //Token = null; + SessionId = null; + } + + protected override async Task Run() + { + List tasks = new List(); + tasks.AddRange(_engine.GetTasks(CancelToken)); + tasks.Add(HeartbeatAsync(CancelToken)); + await _taskManager.Start(tasks, _cancelSource).ConfigureAwait(false); + } + protected override Task Cleanup() + { + var ex = _taskManager.Exception; + if (ex == null || (ex as WebSocketException)?.Code != 1012) //if (ex == null || (ex as WebSocketException)?.Code != 1012) + SessionId = null; //Reset session unless close code 1012 + return base.Cleanup(); + } + + protected override async Task ProcessMessage(string json) + { + base.ProcessMessage(json).GetAwaiter().GetResult(); //This is just a CompletedTask, and we need to avoid asyncs in here + + WebSocketMessage msg; + using (var reader = new JsonTextReader(new StringReader(json))) + msg = _serializer.Deserialize(reader, typeof(WebSocketMessage)) as WebSocketMessage; + + if (msg.Sequence.HasValue) + _lastSequence = msg.Sequence.Value; + + var opCode = (OpCodes?)msg.Operation; + switch (opCode) + { + case OpCodes.Dispatch: + { + if (msg.Type == "READY") + SessionId = (msg.Payload as JToken).Value("session_id"); + + OnReceivedDispatch(msg.Type, msg.Payload as JToken); + + if (msg.Type == "READY" || msg.Type == "RESUMED") + { + _heartbeatInterval = (msg.Payload as JToken).Value("heartbeat_interval"); + _reconnects = 0; + await EndConnect().ConfigureAwait(false); //Complete the connect + } + } + break; + case OpCodes.Reconnect: + { + var payload = (msg.Payload as JToken).ToObject(_serializer); + await Reconnect().ConfigureAwait(false); + } + break; + default: + if (opCode != null) + Logger.Warning($"Unknown Opcode: {opCode}"); + else + Logger.Warning($"Received message with no opcode"); + break; + } + } + + public void SendIdentify(string token) + { + var props = new Dictionary + { + ["$device"] = "Discord.Net" + }; + var msg = new IdentifyCommand() + { + Token = token, + Properties = props, + LargeThreshold = _config.LargeThreshold, + UseCompression = true + }; + QueueMessage(msg); + } + + public void SendResume() + => QueueMessage(new ResumeCommand { SessionId = SessionId, Sequence = _lastSequence }); + public override void SendHeartbeat() + => QueueMessage(new HeartbeatCommand()); + public void SendUpdateStatus(long? idleSince, Game? game) + => QueueMessage(new UpdateStatusCommand + { + IdleSince = idleSince, + Game = game != null ? new APIGame { Name = game.Value.Name, Type = game.Value.Type, Url = game.Value.Url } : null + }); + public void SendUpdateVoice(ulong? serverId, ulong? channelId, bool isSelfMuted, bool isSelfDeafened) + => QueueMessage(new UpdateVoiceCommand { GuildId = serverId, ChannelId = channelId, IsSelfMuted = isSelfMuted, IsSelfDeafened = isSelfDeafened }); + public void SendRequestMembers(IEnumerable serverId, string query, int limit) + => QueueMessage(new RequestMembersCommand { GuildId = serverId.ToArray(), Query = query, Limit = limit }); + + //Cancel if either DiscordClient.Disconnect is called, data socket errors or timeout is reached + public override void WaitForConnection(CancellationToken cancelToken) + => base.WaitForConnection(CancellationTokenSource.CreateLinkedTokenSource(cancelToken, CancelToken).Token); + } +} diff --git a/discord.net/src/Discord.Net/Net/WebSockets/IWebSocketEngine.cs b/discord.net/src/Discord.Net/Net/WebSockets/IWebSocketEngine.cs new file mode 100644 index 000000000..68f31f12b --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSockets/IWebSocketEngine.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + public interface IWebSocketEngine + { + event EventHandler BinaryMessage; + event EventHandler TextMessage; + + Task Connect(string host, CancellationToken cancelToken); + Task Disconnect(); + void QueueMessage(string message); + IEnumerable GetTasks(CancellationToken cancelToken); + } +} diff --git a/discord.net/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs b/discord.net/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs new file mode 100644 index 000000000..e4e186044 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSockets/TextMessageEventArgs.cs @@ -0,0 +1,11 @@ +using System; + +namespace Discord.Net.WebSockets +{ + public class TextMessageEventArgs : EventArgs + { + public string Message { get; } + + public TextMessageEventArgs(string msg) { Message = msg; } + } +} diff --git a/discord.net/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs b/discord.net/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs new file mode 100644 index 000000000..d72e59168 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSockets/WS4NetEngine.cs @@ -0,0 +1,141 @@ +#if !NETSTANDARD1_3 +using SuperSocket.ClientEngine; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using WebSocket4Net; +using WebSocketClient = WebSocket4Net.WebSocket; + +namespace Discord.Net.WebSockets +{ + internal class WS4NetEngine : IWebSocketEngine + { + private readonly DiscordConfig _config; + private readonly ConcurrentQueue _sendQueue; + private readonly TaskManager _taskManager; + private WebSocketClient _webSocket; + private ManualResetEventSlim _waitUntilConnect, _waitUntilDisconnect; + + public event EventHandler BinaryMessage = delegate { }; + public event EventHandler TextMessage = delegate { }; + private void OnBinaryMessage(byte[] data) + => BinaryMessage(this, new BinaryMessageEventArgs(data)); + private void OnTextMessage(string msg) + => TextMessage(this, new TextMessageEventArgs(msg)); + + internal WS4NetEngine(DiscordConfig config, TaskManager taskManager) + { + _config = config; + _taskManager = taskManager; + _sendQueue = new ConcurrentQueue(); + _waitUntilConnect = new ManualResetEventSlim(); + _waitUntilDisconnect = new ManualResetEventSlim(true); + } + + public Task Connect(string host, CancellationToken cancelToken) + { + try + { + _webSocket = new WebSocketClient(host); + _webSocket.EnableAutoSendPing = false; + _webSocket.NoDelay = true; + _webSocket.Proxy = null; + + _webSocket.DataReceived += OnWebSocketBinary; + _webSocket.MessageReceived += OnWebSocketText; + _webSocket.Error += OnWebSocketError; + _webSocket.Closed += OnWebSocketClosed; + _webSocket.Opened += OnWebSocketOpened; + + _waitUntilConnect.Reset(); + _waitUntilDisconnect.Reset(); + _webSocket.Open(); + _waitUntilConnect.Wait(cancelToken); + _taskManager.ThrowException(); //In case our connection failed + } + catch + { + _waitUntilDisconnect.Set(); + throw; + } + return TaskHelper.CompletedTask; + } + + public Task Disconnect() + { + string ignored; + while (_sendQueue.TryDequeue(out ignored)) { } + + var socket = _webSocket; + _webSocket = null; + if (socket != null) + { + socket.Close(); + socket.Opened -= OnWebSocketOpened; + socket.DataReceived -= OnWebSocketBinary; + socket.MessageReceived -= OnWebSocketText; + + _waitUntilDisconnect.Wait(); //We need the next two events to raise this one + socket.Error -= OnWebSocketError; + socket.Closed -= OnWebSocketClosed; + socket.Dispose(); + } + + return TaskHelper.CompletedTask; + } + + private async void OnWebSocketError(object sender, ErrorEventArgs e) + { + await _taskManager.SignalError(e.Exception).ConfigureAwait(false); + _waitUntilConnect.Set(); + _waitUntilDisconnect.Set(); + } + private async void OnWebSocketClosed(object sender, EventArgs e) + { + Exception ex; + if (e is ClosedEventArgs) + ex = new WebSocketException((e as ClosedEventArgs).Code, (e as ClosedEventArgs).Reason); + else + ex = new Exception("Connection lost"); + await _taskManager.SignalError(ex).ConfigureAwait(false); + _waitUntilConnect.Set(); + _waitUntilDisconnect.Set(); + } + private void OnWebSocketOpened(object sender, EventArgs e) + { + _waitUntilConnect.Set(); + _waitUntilDisconnect.Reset(); + } + private void OnWebSocketText(object sender, MessageReceivedEventArgs e) + => OnTextMessage(e.Message); + private void OnWebSocketBinary(object sender, DataReceivedEventArgs e) + => OnBinaryMessage(e.Data); + + public IEnumerable GetTasks(CancellationToken cancelToken) + => new Task[] { SendAsync(cancelToken) }; + + private Task SendAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + try + { + while (!cancelToken.IsCancellationRequested) + { + string json; + while (_sendQueue.TryDequeue(out json)) + _webSocket.Send(json); + await Task.Delay(DiscordConfig.WebSocketQueueInterval, cancelToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + + public void QueueMessage(string message) + => _sendQueue.Enqueue(message); + } +} +#endif \ No newline at end of file diff --git a/discord.net/src/Discord.Net/Net/WebSockets/WebSocket.cs b/discord.net/src/Discord.Net/Net/WebSockets/WebSocket.cs new file mode 100644 index 000000000..5bb4208e5 --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSockets/WebSocket.cs @@ -0,0 +1,188 @@ +using Discord.API.Client; +using Discord.Logging; +using Newtonsoft.Json; +using Nito.AsyncEx; +using System; +using System.IO; +using System.IO.Compression; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Net.WebSockets +{ + public abstract partial class WebSocket + { + private readonly AsyncLock _lock; + protected readonly IWebSocketEngine _engine; + protected readonly DiscordConfig _config; + protected readonly ManualResetEventSlim _connectedEvent; + protected readonly TaskManager _taskManager; + protected readonly JsonSerializer _serializer; + protected CancellationTokenSource _cancelSource; + protected CancellationToken _parentCancelToken; + protected int _heartbeatInterval; + private DateTime _lastHeartbeat; + + /// Gets the logger used for this client. + protected internal Logger Logger { get; } + public CancellationToken CancelToken { get; private set; } + + public string Host { get; set; } + /// Gets the current connection state of this client. + public ConnectionState State { get; private set; } + + public event EventHandler Connected = delegate { }; + private void OnConnected() + => Connected(this, EventArgs.Empty); + public event EventHandler Disconnected = delegate { }; + private void OnDisconnected(bool wasUnexpected, Exception error) + => Disconnected(this, new DisconnectedEventArgs(wasUnexpected, error)); + + public WebSocket(DiscordConfig config, JsonSerializer serializer, Logger logger) + { + _config = config; + _serializer = serializer; + Logger = logger; + + _lock = new AsyncLock(); + _taskManager = new TaskManager(Cleanup); + CancelToken = new CancellationToken(true); + _connectedEvent = new ManualResetEventSlim(false); + +#if !NETSTANDARD1_3 + _engine = new WS4NetEngine(config, _taskManager); +#else + _engine = new BuiltInEngine(config); +#endif + _engine.BinaryMessage += (s, e) => + { + using (var compressed = new MemoryStream(e.Data, 2, e.Data.Length - 2)) + using (var decompressed = new MemoryStream()) + { + using (var zlib = new DeflateStream(compressed, CompressionMode.Decompress)) + zlib.CopyTo(decompressed); + decompressed.Position = 0; + using (var reader = new StreamReader(decompressed)) + ProcessMessage(reader.ReadToEnd()).GetAwaiter().GetResult(); + } + }; + _engine.TextMessage += (s, e) => ProcessMessage(e.Message).Wait(); + } + + protected async Task BeginConnect(CancellationToken parentCancelToken) + { + try + { + using (await _lock.LockAsync().ConfigureAwait(false)) + { + _parentCancelToken = parentCancelToken; + + await _taskManager.Stop().ConfigureAwait(false); + _taskManager.ClearException(); + State = ConnectionState.Connecting; + + _cancelSource = new CancellationTokenSource(); + CancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelSource.Token, parentCancelToken).Token; + _lastHeartbeat = DateTime.UtcNow; + + await _engine.Connect(Host, CancelToken).ConfigureAwait(false); + await Run().ConfigureAwait(false); + } + } + catch (Exception ex) + { + //TODO: Should this be inside the lock? + await _taskManager.SignalError(ex).ConfigureAwait(false); + throw; + } + } + protected async Task EndConnect() + { + try + { + State = ConnectionState.Connected; + Logger.Info($"Connected"); + + OnConnected(); + _connectedEvent.Set(); + } + catch (Exception ex) + { + await _taskManager.SignalError(ex).ConfigureAwait(false); + } + } + + protected abstract Task Run(); + protected virtual async Task Cleanup() + { + var oldState = State; + State = ConnectionState.Disconnecting; + + await _engine.Disconnect().ConfigureAwait(false); + _cancelSource = null; + _connectedEvent.Reset(); + + if (oldState == ConnectionState.Connecting || oldState == ConnectionState.Connected) + { + var ex = _taskManager.Exception; + if (ex == null) + Logger.Info("Disconnected"); + else + Logger.Error("Disconnected", ex); + State = ConnectionState.Disconnected; + OnDisconnected(!_taskManager.WasStopExpected, _taskManager.Exception); + } + else + State = ConnectionState.Disconnected; + } + + protected virtual Task ProcessMessage(string json) + { + return TaskHelper.CompletedTask; + } + protected void QueueMessage(IWebSocketMessage message) + { + string json = JsonConvert.SerializeObject(new WebSocketMessage(message)); + _engine.QueueMessage(json); + } + + protected Task HeartbeatAsync(CancellationToken cancelToken) + { + return Task.Run(async () => + { + try + { + while (!cancelToken.IsCancellationRequested) + { + if (this.State == ConnectionState.Connected && _heartbeatInterval > 0) + { + SendHeartbeat(); + await Task.Delay(_heartbeatInterval, cancelToken).ConfigureAwait(false); + } + else + await Task.Delay(1000, cancelToken).ConfigureAwait(false); + } + } + catch (OperationCanceledException) { } + }); + } + public abstract void SendHeartbeat(); + + public virtual void WaitForConnection(CancellationToken cancelToken) + { + try + { + if (!_connectedEvent.Wait(_config.ConnectionTimeout, cancelToken)) + { + if (State != ConnectionState.Connected) + throw new TimeoutException(); + } + } + catch (OperationCanceledException) + { + _taskManager.ThrowException(); //Throws data socket's internal error if any occured + throw; + } + } + } +} diff --git a/discord.net/src/Discord.Net/Net/WebSockets/WebSocketEventEventArgs.cs b/discord.net/src/Discord.Net/Net/WebSockets/WebSocketEventEventArgs.cs new file mode 100644 index 000000000..a0c60edcf --- /dev/null +++ b/discord.net/src/Discord.Net/Net/WebSockets/WebSocketEventEventArgs.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json.Linq; +using System; + +namespace Discord.Net.WebSockets +{ + public class WebSocketEventEventArgs : EventArgs + { + public string Type { get; } + public JToken Payload { get; } + + internal WebSocketEventEventArgs(string type, JToken data) + { + Type = type; + Payload = data; + } + } +} diff --git a/discord.net/src/Discord.Net/ServiceCollection.cs b/discord.net/src/Discord.Net/ServiceCollection.cs new file mode 100644 index 000000000..104f91dd4 --- /dev/null +++ b/discord.net/src/Discord.Net/ServiceCollection.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Discord +{ + internal class ServiceCollection : IEnumerable + { + private readonly Dictionary _services; + + internal DiscordClient Client { get; } + + internal ServiceCollection(DiscordClient client) + { + Client = client; + _services = new Dictionary(); + } + + public T Add(T service) + where T : class, IService + { + _services.Add(typeof(T), service); + service.Install(Client); + return service; + } + + public T Get(bool isRequired = true) + where T : class, IService + { + IService service; + T singletonT = null; + + if (_services.TryGetValue(typeof(T), out service)) + singletonT = service as T; + + if (singletonT == null && isRequired) + throw new InvalidOperationException($"This operation requires {typeof(T).Name} to be added to {nameof(DiscordClient)}."); + return singletonT; + } + + public IEnumerator GetEnumerator() => _services.Values.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _services.Values.GetEnumerator(); + } +} diff --git a/discord.net/src/Discord.Net/TaskManager.cs b/discord.net/src/Discord.Net/TaskManager.cs new file mode 100644 index 000000000..fa4d0a52f --- /dev/null +++ b/discord.net/src/Discord.Net/TaskManager.cs @@ -0,0 +1,180 @@ +using Nito.AsyncEx; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord +{ + /// Helper class used to manage several tasks and keep them in sync. If any single task errors or stops, all other tasks will also be stopped. + public class TaskManager + { + private readonly AsyncLock _lock; + private readonly Func _stopAction; + private ExceptionDispatchInfo _stopReason; + + private CancellationTokenSource _cancelSource; + private Task _task; + + public bool StopOnCompletion { get; } + public bool WasStopExpected { get; private set; } + public CancellationToken CancelToken => _cancelSource.Token; + + public Exception Exception => _stopReason?.SourceException; + + internal TaskManager(bool stopOnCompletion) + { + _lock = new AsyncLock(); + StopOnCompletion = stopOnCompletion; + } + public TaskManager(Action stopAction, bool stopOnCompletion = true) + : this(stopOnCompletion) + { + _stopAction = TaskHelper.ToAsync(stopAction); + } + public TaskManager(Func stopAction, bool stopOnCompletion = true) + : this(stopOnCompletion) + { + _stopAction = stopAction; + } + + public async Task Start(IEnumerable tasks, CancellationTokenSource cancelSource) + { + if (tasks == null) throw new ArgumentNullException(nameof(tasks)); + if (cancelSource == null) throw new ArgumentNullException(nameof(cancelSource)); + + while (true) + { + var task = _task; + if (task != null) + await Stop().ConfigureAwait(false); + + using (await _lock.LockAsync().ConfigureAwait(false)) + { + _cancelSource = cancelSource; + + if (_task != null) + continue; //Another thread sneaked in and started this manager before we got a lock, loop and try again + + _stopReason = null; + WasStopExpected = false; + + Task[] tasksArray = tasks.ToArray(); + + _task = Task.Run(async () => + { + if (tasksArray.Length > 0) + { + Task anyTask = tasksArray.Length > 0 ? Task.WhenAny(tasksArray) : null; + Task allTasks = tasksArray.Length > 0 ? Task.WhenAll(tasksArray) : null; + //Wait for the first task to stop or error + Task firstTask = await anyTask.ConfigureAwait(false); + + //Signal the rest of the tasks to stop + if (firstTask.Exception != null) + await SignalError(firstTask.Exception).ConfigureAwait(false); + else if (StopOnCompletion) //Unless we allow for natural completions + await SignalStop().ConfigureAwait(false); + + //Wait for the other tasks, and signal their errors too just in case + try { await allTasks.ConfigureAwait(false); } + catch (AggregateException ex) { await SignalError(ex.InnerExceptions.First()).ConfigureAwait(false); } + catch (Exception ex) { await SignalError(ex).ConfigureAwait(false); } + } + + if (!StopOnCompletion && !_cancelSource.IsCancellationRequested) + { + try { await Task.Delay(-1, _cancelSource.Token).ConfigureAwait(false); } //Pause until TaskManager is stopped + catch (OperationCanceledException) { } + } + + //Run the cleanup function within our lock + if (_stopAction != null) + await _stopAction().ConfigureAwait(false); + _task = null; + _cancelSource = null; + }); + return; + } + } + } + + public async Task SignalStop(bool isExpected = false) + { + using (await _lock.LockAsync().ConfigureAwait(false)) + { + if (isExpected) + WasStopExpected = true; + + Cancel(); + } + } + public async Task Stop(bool isExpected = false) + { + Task task; + using (await _lock.LockAsync().ConfigureAwait(false)) + { + if (isExpected) + WasStopExpected = true; + + //Cache the task so we still have something to await if Cleanup is run really quickly + task = _task ?? TaskHelper.CompletedTask; + Cancel(); + } + await task.ConfigureAwait(false); + } + + public async Task SignalError(Exception ex) + { + using (await _lock.LockAsync().ConfigureAwait(false)) + { + if (_stopReason != null) return; + + Cancel(ex); + } + } + public async Task Error(Exception ex) + { + Task task; + using (await _lock.LockAsync().ConfigureAwait(false)) + { + if (_stopReason != null) return; + + //Cache the task so we still have something to await if Cleanup is run really quickly + task = _task ?? TaskHelper.CompletedTask; + Cancel(ex); + } + await task.ConfigureAwait(false); + } + private void Cancel(Exception ex = null) + { + var source = _cancelSource; + if (source != null && !source.IsCancellationRequested) + { + if (ex != null) + _stopReason = ExceptionDispatchInfo.Capture(ex); + _cancelSource.Cancel(); + } + } + + /// Throws an exception if one was captured. + public void ThrowException() + { + using (_lock.Lock()) + { + if (!WasStopExpected) + _stopReason?.Throw(); + } + } + public void ClearException() + { + using (_lock.Lock()) + { + _stopReason = null; + WasStopExpected = false; + } + } + } +} diff --git a/discord.net/src/Discord.Net/project.json b/discord.net/src/Discord.Net/project.json new file mode 100644 index 000000000..887bf6f93 --- /dev/null +++ b/discord.net/src/Discord.Net/project.json @@ -0,0 +1,64 @@ +{ + "version": "0.9.2", + "description": "An unofficial .Net API wrapper for the Discord client.", + "authors": [ "RogueException" ], + + "packOptions": { + "tags": [ + "discord", + "discordapp" + ], + "projectUrl": "https://github.com/RogueException/Discord.Net", + "licenseUrl": "http://opensource.org/licenses/MIT", + "repository": { + "type": "git", + "url": "git://github.com/RogueException/Discord.Net" + } + }, + + "buildOptions": { + "compile": [ "**/*.cs", "../Discord.Net.Shared/*.cs" ], + "allowUnsafe": true, + "warningsAsErrors": true + }, + + "dependencies": { + "Newtonsoft.Json": "8.0.3", + "Nito.AsyncEx": "3.0.1" + }, + + "frameworks": { + "netstandard1.3": { + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-24027", + "System.Net.Requests": "4.0.11-rc2-24027", + "System.Net.Websockets.Client": "4.0.0-rc2-24027", + "System.Reflection.Emit.Lightweight": "4.0.1-rc2-24027", + "System.Runtime.Serialization.Primitives": "4.1.1-rc2-24027", + "System.Security.Cryptography.Algorithms": "4.1.0-rc2-24027", + "System.Net.NameResolution": "4.0.0-rc2-24027" + }, + "imports": [ + "dotnet5.4", + "dnxcore50", + "portable-net45+win8" + ] + }, + "net45": { + "frameworkAssemblies": { + "System.Runtime": { + "type": "build", + "version": "" + }, + "System.Threading.Tasks": { + "type": "build", + "version": "" + } + }, + "dependencies": { + "WebSocket4Net": "0.14.1", + "RestSharp": "105.2.3" + } + } + } +} \ No newline at end of file diff --git a/discord.net/test/Discord.Net.Tests/Discord.Net.Tests.csproj b/discord.net/test/Discord.Net.Tests/Discord.Net.Tests.csproj new file mode 100644 index 000000000..dd0e3f165 --- /dev/null +++ b/discord.net/test/Discord.Net.Tests/Discord.Net.Tests.csproj @@ -0,0 +1,97 @@ + + + + Debug + AnyCPU + {855D6B1D-847B-42DA-BE6A-23683EA89511} + Library + Properties + Discord.Tests + Discord.Net.Tests + v4.5 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + {8d71a857-879a-4a10-859e-5ff824ed6688} + Discord.Net + + + + + + + False + + + False + + + False + + + False + + + + + + + + \ No newline at end of file diff --git a/discord.net/test/Discord.Net.Tests/Properties/AssemblyInfo.cs b/discord.net/test/Discord.Net.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..5b1c7b125 --- /dev/null +++ b/discord.net/test/Discord.Net.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +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: AssemblyTitle("Discord.Net.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Discord.Net.Tests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[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("855d6b1d-847b-42da-be6a-23683ea89511")] + +// 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.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/discord.net/test/Discord.Net.Tests/Tests.cs b/discord.net/test/Discord.Net.Tests/Tests.cs new file mode 100644 index 000000000..64425221d --- /dev/null +++ b/discord.net/test/Discord.Net.Tests/Tests.cs @@ -0,0 +1,490 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Tests +{ + //TODO: Tests are massively incomplete and out of date, needing a full rewrite + + [TestClass] + public class Tests + { + private const int EventTimeout = 10000; //Max time in milliseconds to wait for an event response from our test actions + + private static DiscordClient _hostClient, _targetBot, _observerBot; + private static Server _testServer; + private static Channel _testServerChannel; + private static Random _random; + private static Invite _testServerInvite; + + private static TestContext _context; + + private static string HostBotToken; + private static string ObserverBotToken; + private static string TargetEmail; + private static string TargetPassword; + + public static string RandomText => $"test_{_random.Next()}"; + + #region Initialization + + [ClassInitialize] + public static void Initialize(TestContext testContext) + { + _context = testContext; + + HostBotToken = Environment.GetEnvironmentVariable("discord-unit-host_token"); + ObserverBotToken = Environment.GetEnvironmentVariable("discord-unit-observer_token"); + TargetEmail = Environment.GetEnvironmentVariable("discord-unit-target_email"); + TargetPassword = Environment.GetEnvironmentVariable("discord-unit-target_pass"); + } + + [TestMethod] + [Priority(1)] + public async Task TestInitialize() + { + _context.WriteLine("Initializing."); + + _random = new Random(); + + _hostClient = new DiscordClient(); + _targetBot = new DiscordClient(); + _observerBot = new DiscordClient(); + + await _hostClient.Connect(HostBotToken); + + await Task.Delay(3000); + + //Cleanup existing servers + _hostClient.Servers.Select(x => x.IsOwner ? x.Delete() : x.Leave()); + + //Create new server and invite the other bots to it + + _testServer = await _hostClient.CreateServer("Discord.Net Testing", _hostClient.Regions.First()); + + await Task.Delay(1000); + + Invite invite = await _testServer.CreateInvite(60, 3, false, false); + _testServerInvite = invite; + + _context.WriteLine($"Host: {_hostClient.CurrentUser.Name} in {_hostClient.Servers.Count()}"); + } + + [TestMethod] + [Priority(2)] + public async Task TestTokenLogin_Ready() + { + AssertEvent( + "READY never received", + async () => await _observerBot.Connect(ObserverBotToken), + x => _observerBot.Ready += x, + x => _observerBot.Ready -= x, + null, + true); + _observerBot.Servers.Select(x => x.IsOwner ? x.Delete() : x.Leave()); + await (await _observerBot.GetInvite(_testServerInvite.Code)).Accept(); + } + + [TestMethod] + [Priority(2)] + public void TestReady() + { + AssertEvent( + "READY never received", + async () => await _targetBot.Connect(TargetEmail, TargetPassword), + x => _targetBot.Ready += x, + x => _targetBot.Ready -= x, + null, + true); + + _targetBot.Servers.Select(x => x.IsOwner ? x.Delete() : x.Leave()); + _testServerChannel = _testServer.DefaultChannel; + } + + #endregion + + // Servers + + #region Server Tests + + [TestMethod] + [Priority(3)] + public void TestJoinedServer() + { + AssertEvent( + "Never Got JoinedServer", + async () => await (await _targetBot.GetInvite(_testServerInvite.Code)).Accept(), + x => _targetBot.JoinedServer += x, + x => _targetBot.JoinedServer -= x); + } + + #endregion + + #region Channel Tests + + //Channels + [TestMethod] + public void TestCreateTextChannel() + => TestCreateChannel(ChannelType.Text); + [TestMethod] + public void TestCreateVoiceChannel() + => TestCreateChannel(ChannelType.Voice); + private void TestCreateChannel(ChannelType type) + { + _context.WriteLine($"Host: {_hostClient.CurrentUser.Name} in {_hostClient.Servers.Count()}"); + _context.WriteLine($"Target: {_targetBot.CurrentUser.Name} in {_targetBot.Servers.Count()}"); + _context.WriteLine($"Observer: {_observerBot.CurrentUser.Name} in {_observerBot.Servers.Count()}"); + Channel channel = null; + string name = $"test_{_random.Next()}"; + AssertEvent( + "ChannelCreated event never received", + async () => channel = await _testServer.CreateChannel(name, type), + x => _targetBot.ChannelCreated += x, + x => _targetBot.ChannelCreated -= x, + (s, e) => e.Channel.Name == name); + + AssertEvent( + "ChannelDestroyed event never received", + async () => await channel.Delete(), + x => _targetBot.ChannelDestroyed += x, + x => _targetBot.ChannelDestroyed -= x, + (s, e) => e.Channel.Name == name); + } + + [TestMethod] + [ExpectedException(typeof(Net.HttpException))] + public async Task TestCreateChannel_NoName() + { + await _testServer.CreateChannel($"", ChannelType.Text); + } + [TestMethod] + [ExpectedException(typeof(Net.HttpException))] + public async Task TestCreateChannel_NoType() + { + string name = $"#test_{_random.Next()}"; + await _testServer.CreateChannel($"", ChannelType.FromString("")); + } + [TestMethod] + [ExpectedException(typeof(Net.HttpException))] + public async Task TestCreateChannel_BadType() + { + string name = $"#test_{_random.Next()}"; + await _testServer.CreateChannel($"", ChannelType.FromString("badtype")); + } + [TestMethod] + public async Task Test_CreateGetChannel() + { + var name = $"test_{_random.Next()}"; + var channel = await _testServer.CreateChannel(name, ChannelType.Text); + var get_channel = _testServer.GetChannel(channel.Id); + Assert.AreEqual(channel.Id, get_channel.Id, "ID of Channel and GetChannel were not equal."); + } + [TestMethod] + public void TestSendTyping() + { + var channel = _testServerChannel; + AssertEvent( + "UserUpdated event never fired.", + async () => await channel.SendIsTyping(), + x => _targetBot.UserIsTyping += x, + x => _targetBot.UserIsTyping -= x); + } + [TestMethod] + public void TestEditChannel() + { + var channel = _testServerChannel; + AssertEvent( + "ChannelUpdated Never Received", + async () => await channel.Edit(RandomText, $"topic - {RandomText}", 26), + x => _targetBot.ChannelUpdated += x, + x => _targetBot.ChannelUpdated -= x); + } + [TestMethod] + public void TestChannelMention() + { + var channel = _testServerChannel; + Assert.AreEqual($"<#{channel.Id}>", channel.Mention, "Generated channel mention was not the expected channel mention."); + } + [TestMethod] + public void TestChannelUserCount() + { + Assert.AreEqual(3, _testServerChannel.Users.Count(), "Read an incorrect number of users in a channel"); + } + + #endregion + + #region Message Tests + + //Messages + [TestMethod] + public void TestMessageEvents() + { + string name = $"test_{_random.Next()}"; + var channel = _testServer.CreateChannel(name, ChannelType.Text).Result; + _context.WriteLine($"Channel Name: {channel.Name} / {channel.Server.Name}"); + string text = $"test_{_random.Next()}"; + Message message = null; + AssertEvent( + "MessageCreated event never received", + async () => message = await channel.SendMessage(text), + x => _targetBot.MessageReceived += x, + x => _targetBot.MessageReceived -= x, + (s, e) => e.Message.Text == text); + + AssertEvent( + "MessageUpdated event never received", + async () => await message.Edit(text + " updated"), + x => _targetBot.MessageUpdated += x, + x => _targetBot.MessageUpdated -= x, + (s, e) => e.Before.Text == text && e.After.Text == text + " updated"); + + AssertEvent( + "MessageDeleted event never received", + async () => await message.Delete(), + x => _targetBot.MessageDeleted += x, + x => _targetBot.MessageDeleted -= x, + (s, e) => e.Message.Id == message.Id); + } + [TestMethod] + public async Task TestDownloadMessages_WithCache() + { + string name = $"test_{_random.Next()}"; + var channel = await _testServer.CreateChannel(name, ChannelType.Text); + for (var i = 0; i < 10; i++) await channel.SendMessage(RandomText); + while (channel.Client.MessageQueue.Count > 0) await Task.Delay(100); + var messages = await channel.DownloadMessages(10); + Assert.AreEqual(10, messages.Count(), "Expected 10 messages in downloaded array, did not see 10."); + } + [TestMethod] + public async Task TestDownloadMessages_WithoutCache() + { + string name = $"test_{_random.Next()}"; + var channel = await _testServer.CreateChannel(name, ChannelType.Text); + for (var i = 0; i < 10; i++) await channel.SendMessage(RandomText); + while (channel.Client.MessageQueue.Count > 0) await Task.Delay(100); + var messages = await channel.DownloadMessages(10, useCache: false); + Assert.AreEqual(10, messages.Count(), "Expected 10 messages in downloaded array, did not see 10."); + } + [TestMethod] + public async Task TestSendTTSMessage() + { + var channel = await _testServer.CreateChannel(RandomText, ChannelType.Text); + AssertEvent( + "MessageCreated event never fired", + async () => await channel.SendTTSMessage(RandomText), + x => _targetBot.MessageReceived += x, + x => _targetBot.MessageReceived -= x, + (s, e) => e.Message.IsTTS); + } + + #endregion + + #region User Tests + + [TestMethod] + public void TestUserMentions() + { + var user = _targetBot.GetServer(_testServer.Id).CurrentUser; + Assert.AreEqual($"<@{user.Id}>", user.Mention); + } + [TestMethod] + public void TestUserEdit() + { + var user = _testServer.GetUser(_targetBot.CurrentUser.Id); + AssertEvent( + "UserUpdated never fired", + async () => await user.Edit(true, true, null, null), + x => _targetBot.UserUpdated += x, + x => _targetBot.UserUpdated -= x); + } + [TestMethod] + public void TestEditSelf() + { + var name = $"test_{_random.Next()}"; + AssertEvent( + "UserUpdated never fired", + async () => await _targetBot.CurrentUser.Edit(TargetPassword, name), + x => _observerBot.UserUpdated += x, + x => _observerBot.UserUpdated -= x, + (s, e) => e.After.Name == name); + } + [TestMethod] + public void TestSetStatus() + { + AssertEvent( + "UserUpdated never fired", + async () => await SetStatus(_targetBot, UserStatus.Idle), + x => _observerBot.UserUpdated += x, + x => _observerBot.UserUpdated -= x, + (s, e) => e.After.Status == UserStatus.Idle); + } + private async Task SetStatus(DiscordClient _client, UserStatus status) + { + _client.SetStatus(status); + await Task.Delay(50); + } + [TestMethod] + public void TestSetGame() + { + AssertEvent( + "UserUpdated never fired", + async () => await SetGame(_targetBot, "test game"), + x => _observerBot.UserUpdated += x, + x => _observerBot.UserUpdated -= x, + (s, e) => _targetBot.CurrentGame.Name == "test game"); + + } + private async Task SetGame(DiscordClient _client, string game) + { + _client.SetGame(game); + await Task.Delay(5); + } + + #endregion + + #region Permission Tests + + // Permissions + [TestMethod] + public async Task Test_AddGet_PermissionsRule() + { + var channel = await _testServer.CreateChannel($"test_{_random.Next()}", ChannelType.Text); + var user = _testServer.GetUser(_targetBot.CurrentUser.Id); + var perms = new ChannelPermissionOverrides(sendMessages: PermValue.Deny); + await channel.AddPermissionsRule(user, perms); + var resultPerms = channel.GetPermissionsRule(user); + Assert.IsNotNull(resultPerms, "Perms retrieved from server were null."); + } + [TestMethod] + public async Task Test_AddRemove_PermissionsRule() + { + var channel = await _testServer.CreateChannel($"test_{_random.Next()}", ChannelType.Text); + var user = _testServer.GetUser(_targetBot.CurrentUser.Id); + var perms = new ChannelPermissionOverrides(sendMessages: PermValue.Deny); + await channel.AddPermissionsRule(user, perms); + await channel.RemovePermissionsRule(user); + await Task.Delay(200); + Assert.AreEqual(PermValue.Inherit, channel.GetPermissionsRule(user).SendMessages); + } + [TestMethod] + public async Task Test_Permissions_Event() + { + var channel = await _testServer.CreateChannel($"test_{_random.Next()}", ChannelType.Text); + var user = _testServer.GetUser(_targetBot.CurrentUser.Id); + var perms = new ChannelPermissionOverrides(sendMessages: PermValue.Deny); + AssertEvent + ("ChannelUpdatedEvent never fired.", + async () => await channel.AddPermissionsRule(user, perms), + x => _targetBot.ChannelUpdated += x, + x => _targetBot.ChannelUpdated -= x, + (s, e) => e.After.PermissionOverwrites.Count() != e.Before.PermissionOverwrites.Count()); + } + [TestMethod] + [ExpectedException(typeof(Net.HttpException))] + public async Task Test_Affect_Permissions_Invalid_Channel() + { + var channel = await _testServer.CreateChannel($"test_{_random.Next()}", ChannelType.Text); + var user = _testServer.GetUser(_targetBot.CurrentUser.Id); + var perms = new ChannelPermissionOverrides(sendMessages: PermValue.Deny); + await channel.Delete(); + await channel.AddPermissionsRule(user, perms); + } + + #endregion + + + [ClassCleanup] + public static void Cleanup() + { + WaitMany( + _hostClient.State == ConnectionState.Connected ? _hostClient.Servers.Select(x => x.IsOwner ? x.Delete() : x.Leave()) : null, + _targetBot.State == ConnectionState.Connected ? _targetBot.Servers.Select(x => x.IsOwner ? x.Delete() : x.Leave()) : null, + _observerBot.State == ConnectionState.Connected ? _observerBot.Servers.Select(x => x.IsOwner ? x.Delete() : x.Leave()) : null); + + WaitAll( + _hostClient.Disconnect(), + _targetBot.Disconnect(), + _observerBot.Disconnect()); + } + + #region Helpers + + // Task Helpers + + private static void AssertEvent(string msg, Func action, Action> addEvent, Action> removeEvent, Func test = null) + { + AssertEvent(msg, action, addEvent, removeEvent, test, true); + } + private static void AssertNoEvent(string msg, Func action, Action> addEvent, Action> removeEvent, Func test = null) + { + AssertEvent(msg, action, addEvent, removeEvent, test, false); + } + private static void AssertEvent(string msg, Func action, Action> addEvent, Action> removeEvent, Func test, bool assertTrue) + { + ManualResetEventSlim trigger = new ManualResetEventSlim(false); + bool result = false; + + EventHandler handler = (s, e) => + { + if (test != null) + { + result |= test(s, e); + trigger.Set(); + } + else + result = true; + }; + + addEvent(handler); + var task = action(); + trigger.Wait(EventTimeout); + task.Wait(); + removeEvent(handler); + + Assert.AreEqual(assertTrue, result, msg); + } + + private static void AssertEvent(string msg, Func action, Action addEvent, Action removeEvent, Func test, bool assertTrue) + { + ManualResetEventSlim trigger = new ManualResetEventSlim(false); + bool result = false; + + EventHandler handler = (s, e) => + { + if (test != null) + { + result |= test(s); + trigger.Set(); + } + else + result = true; + }; + + addEvent(handler); + var task = action(); + trigger.Wait(EventTimeout); + task.Wait(); + removeEvent(handler); + + Assert.AreEqual(assertTrue, result, msg); + } + + private static void WaitAll(params Task[] tasks) + { + Task.WaitAll(tasks); + } + private static void WaitAll(IEnumerable tasks) + { + Task.WaitAll(tasks.ToArray()); + } + private static void WaitMany(params IEnumerable[] tasks) + { + Task.WaitAll(tasks.Where(x => x != null).SelectMany(x => x).ToArray()); + } + + #endregion + } +} diff --git a/discord.net/test/Discord.Net.Tests/config.json.example b/discord.net/test/Discord.Net.Tests/config.json.example new file mode 100644 index 000000000..638d65b4d --- /dev/null +++ b/discord.net/test/Discord.Net.Tests/config.json.example @@ -0,0 +1,14 @@ +{ + "user1": { + "email": "user1@example.com", + "password": "password123" + }, + "user2": { + "email": "user2@example.com", + "password": "password456" + }, + "user3": { + "email": "user3@example.com", + "password": "password789" + } +} \ No newline at end of file diff --git a/discord.net/test/Discord.Net.Tests/packages.config b/discord.net/test/Discord.Net.Tests/packages.config new file mode 100644 index 000000000..2abc396bb --- /dev/null +++ b/discord.net/test/Discord.Net.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/license.md b/license.md index ad9c4e5fe..b177a35dc 100644 --- a/license.md +++ b/license.md @@ -1,4 +1,4 @@ -Copyright (c) 2015 Master Kwoth +Copyright (c) 2016 WizNet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal