diff --git a/DockerGuide.md b/DockerGuide.md index 4b6ec756..5a87bc22 100644 --- a/DockerGuide.md +++ b/DockerGuide.md @@ -43,6 +43,7 @@ Open putty and type ip adress **you got in your email** with port 22 - Type `nano /nadeko/credentials.json` and type in your `credentials` - CTRL+X then CTRL+Y to save - Type `docker start nadeko` +- Type `docker logs -f nadeko` to see the console output **Your bot is running, enjoy! o/** diff --git a/LinuxSetup.md b/LinuxSetup.md index 784e1253..0272affc 100644 --- a/LinuxSetup.md +++ b/LinuxSetup.md @@ -153,7 +153,7 @@ TMUX `certmgr -ssl https://discordapp.com` **14)** -`certmgr --ssl https://gateway.discord.gg` +`certmgr -ssl https://gateway.discord.gg` Type `yes` and hit Enter **(three times - as it will ask for three times)** diff --git a/NadekoBot/Modules/Administration/AdministrationModule.cs b/NadekoBot/Modules/Administration/AdministrationModule.cs index 2ea33354..23402c81 100644 --- a/NadekoBot/Modules/Administration/AdministrationModule.cs +++ b/NadekoBot/Modules/Administration/AdministrationModule.cs @@ -64,7 +64,7 @@ namespace NadekoBot.Modules.Administration commands.ForEach(cmd => cmd.Init(cgb)); cgb.CreateCommand(Prefix + "delmsgoncmd") - .Description($"Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only. | `{Prefix}delmsgoncmd`") + .Description($"Toggles the automatic deletion of user's successful command message to prevent chat flood. **Server Manager Only.** | `{Prefix}delmsgoncmd`") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { @@ -90,7 +90,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "setrole").Alias(Prefix + "sr") - .Description($"Sets a role for a given user. | `{Prefix}sr @User Guest`") + .Description($"Sets a role for a given user. **Needs Manage Roles Permissions.**| `{Prefix}sr @User Guest`") .Parameter("user_name", ParameterType.Required) .Parameter("role_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) @@ -133,7 +133,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "removerole").Alias(Prefix + "rr") - .Description($"Removes a role from a given user. | `{Prefix}rr @User Admin`") + .Description($"Removes a role from a given user. **Needs Manage Roles Permissions.**| `{Prefix}rr @User Admin`") .Parameter("user_name", ParameterType.Required) .Parameter("role_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) @@ -171,7 +171,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "renamerole") .Alias(Prefix + "renr") - .Description($"Renames a role. Role you are renaming must be lower than bot's highest role. | `{Prefix}renr \"First role\" SecondRole`") + .Description($"Renames a role. Roles you are renaming must be lower than bot's highest role. **Manage Roles Permissions.** | `{Prefix}renr \"First role\" SecondRole`") .Parameter("r1", ParameterType.Required) .Parameter("r2", ParameterType.Required) .AddCheck(new SimpleCheckers.ManageRoles()) @@ -204,7 +204,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "removeallroles").Alias(Prefix + "rar") - .Description($"Removes all roles from a mentioned user. | `{Prefix}rar @User`") + .Description($"Removes all roles from a mentioned user. **Needs Manage Roles Permissions.**| `{Prefix}rar @User`") .Parameter("user_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => @@ -230,7 +230,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "createrole").Alias(Prefix + "cr") - .Description($"Creates a role with a given name. | `{Prefix}cr Awesome Role`") + .Description($"Creates a role with a given name. **Needs Manage Roles Permissions.**| `{Prefix}cr Awesome Role`") .Parameter("role_name", ParameterType.Unparsed) .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => @@ -253,7 +253,7 @@ namespace NadekoBot.Modules.Administration .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. | `{Prefix}rc Admin 255 200 100` or `{Prefix}rc Admin ffba55`") + .Description($"Set a role's color to the hex or 0-255 rgb color value provided. **Needs Manage Roles Permissions.** | `{Prefix}rc Admin 255 200 100` or `{Prefix}rc Admin ffba55`") .Do(async e => { if (!e.User.ServerPermissions.ManageRoles) @@ -298,7 +298,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "ban").Alias(Prefix + "b") .Parameter("user", ParameterType.Required) .Parameter("msg", ParameterType.Unparsed) - .Description($"Bans a user by id or name with an optional message. | `{Prefix}b \"@some Guy\" Your behaviour is toxic.`") + .Description($"Bans a user by id or name with an optional message. **Needs Ban Permissions.**| `{Prefix}b \"@some Guy\" Your behaviour is toxic.`") .Do(async e => { var msg = e.GetArg("msg"); @@ -333,7 +333,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "softban").Alias(Prefix + "sb") .Parameter("user", ParameterType.Required) .Parameter("msg", ParameterType.Unparsed) - .Description($"Bans and then unbans a user by id or name with an optional message. | `{Prefix}sb \"@some Guy\" Your behaviour is toxic.`") + .Description($"Bans and then unbans a user by id or name with an optional message. **Needs Ban Permissions.**| `{Prefix}sb \"@some Guy\" Your behaviour is toxic.`") .Do(async e => { var msg = e.GetArg("msg"); @@ -369,7 +369,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "kick").Alias(Prefix + "k") .Parameter("user") .Parameter("msg", ParameterType.Unparsed) - .Description($"Kicks a mentioned user. | `{Prefix}k \"@some Guy\" Your behaviour is toxic.`") + .Description($"Kicks a mentioned user. **Needs Kick Permissions.**| `{Prefix}k \"@some Guy\" Your behaviour is toxic.`") .Do(async e => { var msg = e.GetArg("msg"); @@ -400,7 +400,7 @@ namespace NadekoBot.Modules.Administration } }); cgb.CreateCommand(Prefix + "mute") - .Description($"Mutes mentioned user or users. | `{Prefix}mute \"@Someguy\"` or `{Prefix}mute \"@Someguy\" \"@Someguy\"`") + .Description($"Mutes mentioned user or users. **Needs Mute Permissions.**| `{Prefix}mute \"@Someguy\"` or `{Prefix}mute \"@Someguy\" \"@Someguy\"`") .Parameter("throwaway", ParameterType.Unparsed) .Do(async e => { @@ -426,7 +426,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "unmute") - .Description($"Unmutes mentioned user or users. | `{Prefix}unmute \"@Someguy\"` or `{Prefix}unmute \"@Someguy\" \"@Someguy\"`") + .Description($"Unmutes mentioned user or users. **Needs Mute Permissions.**| `{Prefix}unmute \"@Someguy\"` or `{Prefix}unmute \"@Someguy\" \"@Someguy\"`") .Parameter("throwaway", ParameterType.Unparsed) .Do(async e => { @@ -453,7 +453,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "deafen") .Alias(Prefix + "deaf") - .Description($"Deafens mentioned user or users | `{Prefix}deaf \"@Someguy\"` or `{Prefix}deaf \"@Someguy\" \"@Someguy\"`") + .Description($"Deafens mentioned user or users. **Needs Deafen Permissions.**| `{Prefix}deaf \"@Someguy\"` or `{Prefix}deaf \"@Someguy\" \"@Someguy\"`") .Parameter("throwaway", ParameterType.Unparsed) .Do(async e => { @@ -480,7 +480,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "undeafen") .Alias(Prefix + "undef") - .Description($"Undeafens mentioned user or users | `{Prefix}undef \"@Someguy\"` or `{Prefix}undef \"@Someguy\" \"@Someguy\"`") + .Description($"Undeafens mentioned user or users. **Needs Deafen Permissions.** | `{Prefix}undef \"@Someguy\"` or `{Prefix}undef \"@Someguy\" \"@Someguy\"`") .Parameter("throwaway", ParameterType.Unparsed) .Do(async e => { @@ -507,7 +507,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "delvoichanl") .Alias(Prefix + "dvch") - .Description($"Deletes a voice channel with a given name. | `{Prefix}dvch VoiceChannelName`") + .Description($"Deletes a voice channel with a given name. **Needs Manage Channel Permissions.**| `{Prefix}dvch VoiceChannelName`") .Parameter("channel_name", ParameterType.Required) .Do(async e => { @@ -530,7 +530,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "creatvoichanl") .Alias(Prefix + "cvch") - .Description($"Creates a new voice channel with a given name. | `{Prefix}cvch VoiceChannelName`") + .Description($"Creates a new voice channel with a given name. **Needs Manage Channel Permissions.** | `{Prefix}cvch VoiceChannelName`") .Parameter("channel_name", ParameterType.Required) .Do(async e => { @@ -550,7 +550,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "deltxtchanl") .Alias(Prefix + "dtch") - .Description($"Deletes a text channel with a given name. | `{Prefix}dtch TextChannelName`") + .Description($"Deletes a text channel with a given name. **Needs Manage Channel Permissions.** | `{Prefix}dtch TextChannelName`") .Parameter("channel_name", ParameterType.Required) .Do(async e => { @@ -572,7 +572,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "creatxtchanl") .Alias(Prefix + "ctch") - .Description($"Creates a new text channel with a given name. | `{Prefix}ctch TextChannelName`") + .Description($"Creates a new text channel with a given name. **Needs Manage Channel Permissions.** | `{Prefix}ctch TextChannelName`") .Parameter("channel_name", ParameterType.Required) .Do(async e => { @@ -592,7 +592,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "settopic") .Alias(Prefix + "st") - .Description($"Sets a topic on the current channel. | `{Prefix}st My new topic`") + .Description($"Sets a topic on the current channel. **Needs Manage Channel Permissions.** | `{Prefix}st My new topic`") .AddCheck(SimpleCheckers.ManageChannels()) .Parameter("topic", ParameterType.Unparsed) .Do(async e => @@ -604,7 +604,7 @@ namespace NadekoBot.Modules.Administration cgb.CreateCommand(Prefix + "setchanlname") .Alias(Prefix + "schn") - .Description($"Changed the name of the current channel.| `{Prefix}schn NewName`") + .Description($"Changed the name of the current channel. **Needs Manage Channel Permissions.**| `{Prefix}schn NewName`") .AddCheck(SimpleCheckers.ManageChannels()) .Parameter("name", ParameterType.Unparsed) .Do(async e => @@ -815,7 +815,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "donators") - .Description("List of lovely people who donated to keep this project alive. | `{Prefix}donators`") + .Description($"List of lovely people who donated to keep this project alive. | `{Prefix}donators`") .Do(async e => { await Task.Run(async () => @@ -829,7 +829,7 @@ namespace NadekoBot.Modules.Administration }); cgb.CreateCommand(Prefix + "donadd") - .Description($"Add a donator to the database. | `{Prefix}donadd Donate Amount`") + .Description($"Add a donator to the database. **Kwoth Only** | `{Prefix}donadd Donate Amount`") .Parameter("donator") .Parameter("amount") .AddCheck(SimpleCheckers.OwnerOnly()) diff --git a/NadekoBot/Modules/Administration/Commands/AutoAssignRole.cs b/NadekoBot/Modules/Administration/Commands/AutoAssignRole.cs index 7ccd0e0e..38084dfa 100644 --- a/NadekoBot/Modules/Administration/Commands/AutoAssignRole.cs +++ b/NadekoBot/Modules/Administration/Commands/AutoAssignRole.cs @@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Administration.Commands { cgb.CreateCommand(Module.Prefix + "autoassignrole") .Alias(Module.Prefix + "aar") - .Description($"Automaticaly assigns a specified role to every user who joins the server. |`{Prefix}aar` to disable, `{Prefix}aar Role Name` to enable") + .Description($"Automaticaly assigns a specified role to every user who joins the server. **Needs Manage Roles Permissions.** |`{Prefix}aar` to disable, `{Prefix}aar Role Name` to enable") .Parameter("role", ParameterType.Unparsed) .AddCheck(new SimpleCheckers.ManageRoles()) .Do(async e => diff --git a/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs b/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs index f8dc2133..c08c34ef 100644 --- a/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs +++ b/NadekoBot/Modules/Administration/Commands/CrossServerTextChannel.cs @@ -65,7 +65,7 @@ namespace NadekoBot.Modules.Administration.Commands { cgb.CreateCommand(Module.Prefix + "scsc") .Description("Starts an instance of cross server channel. You will get a token as a DM " + - $"that other people will use to tune in to the same instance. | `{Prefix}scsc`") + $"that other people will use to tune in to the same instance. **Bot Owner Only.** | `{Prefix}scsc`") .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { @@ -79,7 +79,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "jcsc") - .Description($"Joins current channel to an instance of cross server channel using the token. | `{Prefix}jcsc`") + .Description($"Joins current channel to an instance of cross server channel using the token. **Needs Manage Server Permissions.**| `{Prefix}jcsc`") .Parameter("token") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => @@ -95,7 +95,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "lcsc") - .Description($"Leaves Cross server channel instance from this channel. | `{Prefix}lcsc`") + .Description($"Leaves Cross server channel instance from this channel. **Needs Manage Server Permissions.**| `{Prefix}lcsc`") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { diff --git a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs index 5105863f..2725e196 100644 --- a/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs +++ b/NadekoBot/Modules/Administration/Commands/CustomReactionsCommands.cs @@ -146,7 +146,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Prefix + "delcustreact") .Alias(Prefix + "dcr") - .Description($"Deletes a custom reaction with given name (and index). | `{Prefix}dcr index`") + .Description($"Deletes a custom reaction with given name (and index). **Bot Owner Only.**| `{Prefix}dcr index`") .Parameter("name", ParameterType.Required) .Parameter("index", ParameterType.Optional) .AddCheck(SimpleCheckers.OwnerOnly()) diff --git a/NadekoBot/Modules/Administration/Commands/IncidentsCommands.cs b/NadekoBot/Modules/Administration/Commands/IncidentsCommands.cs index d7915bb2..ab93693a 100644 --- a/NadekoBot/Modules/Administration/Commands/IncidentsCommands.cs +++ b/NadekoBot/Modules/Administration/Commands/IncidentsCommands.cs @@ -14,7 +14,7 @@ namespace NadekoBot.Modules.Administration.Commands { cgb.CreateCommand(Module.Prefix + "listincidents") .Alias(Prefix + "lin") - .Description($"List all UNREAD incidents and flags them as read. | `{Prefix}lin`") + .Description($"List all UNREAD incidents and flags them as read. **Needs Manage Server Permissions.**| `{Prefix}lin`") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { @@ -27,7 +27,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Module.Prefix + "listallincidents") .Alias(Prefix + "lain") - .Description($"Sends you a file containing all incidents and flags them as read. | `{Prefix}lain`") + .Description($"Sends you a file containing all incidents and flags them as read. **Needs Manage Server Permissions.**| `{Prefix}lain`") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { diff --git a/NadekoBot/Modules/Administration/Commands/LogCommand.cs b/NadekoBot/Modules/Administration/Commands/LogCommand.cs index db693ae3..b5f19eb1 100644 --- a/NadekoBot/Modules/Administration/Commands/LogCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/LogCommand.cs @@ -375,7 +375,7 @@ namespace NadekoBot.Modules.Administration.Commands { cgb.CreateCommand(Module.Prefix + "spmom") - .Description($"Toggles whether mentions of other offline users on your server will send a pm to them. | `{Prefix}spmom`") + .Description($"Toggles whether mentions of other offline users on your server will send a pm to them. **Needs Manage Server Permissions.**| `{Prefix}spmom`") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { @@ -413,7 +413,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Prefix + "logignore") - .Description($"Toggles whether the {Prefix}logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. | `{Prefix}logignore`") + .Description($"Toggles whether the {Prefix}logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Bot Owner Only!**| `{Prefix}logignore`") .AddCheck(SimpleCheckers.OwnerOnly()) .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => @@ -431,7 +431,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "userpresence") - .Description($"Starts logging to this channel when someone from the server goes online/offline/idle. | `{Prefix}userpresence`") + .Description($"Starts logging to this channel when someone from the server goes online/offline/idle. **Needs Manage Server Permissions.**| `{Prefix}userpresence`") .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => { @@ -447,7 +447,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "voicepresence") - .Description("Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. | `{Prefix}voicerpresence`") + .Description($"Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. **Needs Manage Server Permissions.**| `{Prefix}voicerpresence`") .Parameter("all", ParameterType.Optional) .AddCheck(SimpleCheckers.ManageServer()) .Do(async e => diff --git a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs b/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs index 2ea4346d..e87f5555 100644 --- a/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs +++ b/NadekoBot/Modules/Administration/Commands/MessageRepeater.cs @@ -57,7 +57,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Module.Prefix + "repeatinvoke") .Alias(Module.Prefix + "repinv") - .Description($"Immediately shows the repeat message and restarts the timer. | `{Prefix}repinv`") + .Description($"Immediately shows the repeat message and restarts the timer. **Needs Manage Messages Permissions.**| `{Prefix}repinv`") .AddCheck(SimpleCheckers.ManageMessages()) .Do(async e => { @@ -73,7 +73,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Module.Prefix + "repeat") .Description("Repeat a message every X minutes. If no parameters are specified, " + - $"repeat is disabled. Requires manage messages. |`{Prefix}repeat 5 Hello there`") + $"repeat is disabled. **Needs Manage Messages Permissions.** |`{Prefix}repeat 5 Hello there`") .Parameter("minutes", ParameterType.Optional) .Parameter("msg", ParameterType.Unparsed) .AddCheck(SimpleCheckers.ManageMessages()) diff --git a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs index 4978dbe3..c1caa0bc 100644 --- a/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs +++ b/NadekoBot/Modules/Administration/Commands/PlayingRotate.cs @@ -96,14 +96,14 @@ namespace NadekoBot.Modules.Administration.Commands { cgb.CreateCommand(Module.Prefix + "rotateplaying") .Alias(Module.Prefix + "ropl") - .Description($"Toggles rotation of playing status of the dynamic strings you specified earlier. | `{Prefix}ropl`") + .Description($"Toggles rotation of playing status of the dynamic strings you specified earlier. **Bot Owner Only!** | `{Prefix}ropl`") .AddCheck(SimpleCheckers.OwnerOnly()) .Do(DoFunc()); cgb.CreateCommand(Module.Prefix + "addplaying") .Alias(Module.Prefix + "adpl") .Description("Adds a specified string to the list of playing strings to rotate. " + - "Supported placeholders: " + string.Join(", ", PlayingPlaceholders.Keys)+ $" | `{Prefix}adpl`") + "Supported placeholders: " + string.Join(", ", PlayingPlaceholders.Keys)+ $" **Bot Owner Only!**| `{Prefix}adpl`") .Parameter("text", ParameterType.Unparsed) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => @@ -126,7 +126,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Module.Prefix + "listplaying") .Alias(Module.Prefix + "lipl") - .Description($"Lists all playing statuses with their corresponding number. | `{Prefix}lipl`") + .Description($"Lists all playing statuses with their corresponding number. **Bot Owner Only!**| `{Prefix}lipl`") .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => { @@ -143,7 +143,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Module.Prefix + "removeplaying") .Alias(Module.Prefix + "repl", Module.Prefix + "rmpl") - .Description($"Removes a playing string on a given number. | `{Prefix}rmpl`") + .Description($"Removes a playing string on a given number. **Bot Owner Only!**| `{Prefix}rmpl`") .Parameter("number", ParameterType.Required) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => diff --git a/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs b/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs index c9efe84e..d1f62bd9 100644 --- a/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/RatelimitCommand.cs @@ -41,7 +41,7 @@ namespace NadekoBot.Modules.Administration.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "slowmode") - .Description($"Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. | `{Prefix}slowmode`") + .Description($"Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. **Needs Manage Messages Permissions.**| `{Prefix}slowmode`") .AddCheck(SimpleCheckers.ManageMessages()) .Do(async e => { diff --git a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs b/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs index f88957bd..8e5e54fb 100644 --- a/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/SelfAssignedRolesCommand.cs @@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Administration.Commands { cgb.CreateCommand(Module.Prefix + "asar") .Description("Adds a role, or list of roles separated by whitespace" + - $"(use quotations for multiword roles) to the list of self-assignable roles. | `{Prefix}asar Gamer`") + $"(use quotations for multiword roles) to the list of self-assignable roles. **Needs Manage Roles Permissions.**| `{Prefix}asar Gamer`") .Parameter("roles", ParameterType.Multiple) .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => diff --git a/NadekoBot/Modules/Administration/Commands/SelfCommands.cs b/NadekoBot/Modules/Administration/Commands/SelfCommands.cs index 23322e07..f8019ac9 100644 --- a/NadekoBot/Modules/Administration/Commands/SelfCommands.cs +++ b/NadekoBot/Modules/Administration/Commands/SelfCommands.cs @@ -14,7 +14,7 @@ namespace NadekoBot.Modules.Administration.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "leave") - .Description($"Makes Nadeko leave the server. Either name or id required. | `{Prefix}leave 123123123331`") + .Description($"Makes Nadeko leave the server. Either name or id required. **Bot Owner Only!**| `{Prefix}leave 123123123331`") .Parameter("arg", ParameterType.Required) .AddCheck(SimpleCheckers.OwnerOnly()) .Do(async e => diff --git a/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs b/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs index 5281f9df..b2cdf3b4 100644 --- a/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/ServerGreetCommand.cs @@ -219,7 +219,7 @@ namespace NadekoBot.Modules.Administration.Commands internal override void Init(CommandGroupBuilder cgb) { cgb.CreateCommand(Module.Prefix + "grdel") - .Description($"Toggles automatic deletion of greet and bye messages. | `{Prefix}grdel`") + .Description($"Toggles automatic deletion of greet and bye messages. **Needs Manage Server Permissions.**| `{Prefix}grdel`") .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; @@ -232,7 +232,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "greet") - .Description($"Toggles anouncements on the current channel when someone joins the server. | `{Prefix}greet`") + .Description($"Toggles anouncements on the current channel when someone joins the server. **Needs Manage Server Permissions.**| `{Prefix}greet`") .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; @@ -245,7 +245,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "greetmsg") - .Description($"Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. | `{Prefix}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. **Needs Manage Server Permissions.**| `{Prefix}greetmsg Welcome to the server, %user%.`") .Parameter("msg", ParameterType.Unparsed) .Do(async e => { @@ -278,7 +278,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "byemsg") - .Description($"Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. | `{Prefix}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. **Needs Manage Server Permissions.**| `{Prefix}byemsg %user% has left the server.`") .Parameter("msg", ParameterType.Unparsed) .Do(async e => { @@ -297,7 +297,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "byepm") - .Description($"Toggles whether the good bye messages will be sent in a PM or in the text channel. | `{Prefix}byepm`") + .Description($"Toggles whether the good bye messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `{Prefix}byepm`") .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; @@ -313,7 +313,7 @@ namespace NadekoBot.Modules.Administration.Commands }); cgb.CreateCommand(Module.Prefix + "greetpm") - .Description($"Toggles whether the greet messages will be sent in a PM or in the text channel. | `{Prefix}greetpm`") + .Description($"Toggles whether the greet messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `{Prefix}greetpm`") .Do(async e => { if (!e.User.ServerPermissions.ManageServer) return; diff --git a/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs b/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs index bf5e2a73..4c6a948c 100644 --- a/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs +++ b/NadekoBot/Modules/Administration/Commands/VoicePlusTextCommand.cs @@ -88,7 +88,7 @@ namespace NadekoBot.Modules.Administration.Commands { cgb.CreateCommand(Module.Prefix + "cleanv+t") .Alias(Module.Prefix + "cv+t") - .Description($"Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk.** | `{Prefix}cleanv+t`") + .Description($"Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk.\nNeeds Manage Roles and Manage Channels Permissions.** | `{Prefix}cleanv+t`") .AddCheck(SimpleCheckers.CanManageRoles) .AddCheck(SimpleCheckers.ManageChannels()) .Do(async e => @@ -120,7 +120,7 @@ namespace NadekoBot.Modules.Administration.Commands cgb.CreateCommand(Module.Prefix + "voice+text") .Alias(Module.Prefix + "v+t") .Description("Creates a text channel for each voice channel only users in that voice channel can see." + - $"If you are server owner, keep in mind you will see them all the time regardless. | `{Prefix}voice+text`") + $"If you are server owner, keep in mind you will see them all the time regardless. **Needs Manage Roles and Manage Channels Permissions.**| `{Prefix}voice+text`") .AddCheck(SimpleCheckers.ManageChannels()) .AddCheck(SimpleCheckers.CanManageRoles) .Do(async e => diff --git a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs b/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs index 449c4da6..e14e48b4 100644 --- a/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs +++ b/NadekoBot/Modules/ClashOfClans/ClashOfClansModule.cs @@ -186,7 +186,7 @@ namespace NadekoBot.Modules.ClashOfClans cgb.CreateCommand(Prefix + "startwar") .Alias(Prefix + "sw") - .Description("Starts a war with a given number. | `{Prefix}sw 15`") + .Description($"Starts a war with a given number. | `{Prefix}sw 15`") .Parameter("number", ParameterType.Required) .Do(async e => { diff --git a/NadekoBot/Modules/Music/MusicModule.cs b/NadekoBot/Modules/Music/MusicModule.cs index e0045ab9..3c4aa351 100644 --- a/NadekoBot/Modules/Music/MusicModule.cs +++ b/NadekoBot/Modules/Music/MusicModule.cs @@ -756,7 +756,7 @@ namespace NadekoBot.Modules.Music cgb.CreateCommand(Prefix + "getlink") .Alias(Prefix + "gl") - .Description("Shows a link to the song in the queue by index, or the currently playing song by default. | `{Prefix}gl`") + .Description($"Shows a link to the song in the queue by index, or the currently playing song by default. | `{Prefix}gl`") .Parameter("index", ParameterType.Optional) .Do(async e => { @@ -791,7 +791,7 @@ namespace NadekoBot.Modules.Music cgb.CreateCommand(Prefix + "autoplay") .Alias(Prefix + "ap") - .Description("Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) | `{Prefix}ap`") + .Description($"Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) | `{Prefix}ap`") .Do(async e => { diff --git a/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs b/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs index 412ce69a..0c6aa79b 100644 --- a/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs +++ b/NadekoBot/Modules/Permissions/Classes/PermissionChecker.cs @@ -49,7 +49,6 @@ namespace NadekoBot.Modules.Permissions.Classes { return false; } - if (timeBlackList.Contains(user.Id)) return false; @@ -64,9 +63,9 @@ namespace NadekoBot.Modules.Permissions.Classes PermissionsHandler.PermissionsDict.TryGetValue(user.Server.Id, out perms); AddUserCooldown(user.Server.Id, user.Id, command.Text.ToLower()); - if (commandCooldowns.Keys.Contains(user.Server.Id+":"+command.Text.ToLower())) + if (commandCooldowns.Keys.Contains(user.Server.Id + ":" + command.Text.ToLower())) { - if(perms?.Verbose == true) + if (perms?.Verbose == true) error = $"{user.Mention} You have a cooldown on that command."; return false; } @@ -150,7 +149,8 @@ namespace NadekoBot.Modules.Permissions.Classes } } - public void AddUserCooldown(ulong serverId, ulong userId, string commandName) { + public void AddUserCooldown(ulong serverId, ulong userId, string commandName) + { commandCooldowns.TryAdd(commandName, userId); var tosave = serverId + ":" + commandName; Task.Run(async () => @@ -158,7 +158,8 @@ namespace NadekoBot.Modules.Permissions.Classes ServerPermissions perms; PermissionsHandler.PermissionsDict.TryGetValue(serverId, out perms); int cd; - if (!perms.CommandCooldowns.TryGetValue(commandName,out cd)) { + if (!perms.CommandCooldowns.TryGetValue(commandName, out cd)) + { return; } if (commandCooldowns.TryAdd(tosave, userId)) diff --git a/NadekoBot/Modules/Permissions/PermissionsModule.cs b/NadekoBot/Modules/Permissions/PermissionsModule.cs index d2bc1840..a024341f 100644 --- a/NadekoBot/Modules/Permissions/PermissionsModule.cs +++ b/NadekoBot/Modules/Permissions/PermissionsModule.cs @@ -813,7 +813,7 @@ namespace NadekoBot.Modules.Permissions cgb.CreateCommand(Prefix + "allcmdcooldowns") .Alias(Prefix + "acmdcds") - .Description("Shows a list of all commands and their respective cooldowns. | `{Prefix}acmdcds`") + .Description($"Shows a list of all commands and their respective cooldowns. | `{Prefix}acmdcds`") .Do(async e => { ServerPermissions perms; diff --git a/NadekoBot/Modules/Searches/Commands/EvalCommand.cs b/NadekoBot/Modules/Searches/Commands/EvalCommand.cs index 0d0108e0..4dd5a9fe 100644 --- a/NadekoBot/Modules/Searches/Commands/EvalCommand.cs +++ b/NadekoBot/Modules/Searches/Commands/EvalCommand.cs @@ -17,7 +17,7 @@ namespace NadekoBot.Modules.Searches.Commands { cgb.CreateCommand(Module.Prefix + "calculate") .Alias(Module.Prefix + "calc") - .Description($"Evaluate a mathematical expression. | {Prefix}calc 1+1") + .Description($"Evaluate a mathematical expression. | `{Prefix}calc 1+1`") .Parameter("expression", ParameterType.Unparsed) .Do(EvalFunc()); } diff --git a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs b/NadekoBot/Modules/Searches/Commands/OsuCommands.cs index bda99f48..8ead137d 100644 --- a/NadekoBot/Modules/Searches/Commands/OsuCommands.cs +++ b/NadekoBot/Modules/Searches/Commands/OsuCommands.cs @@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Searches.Commands }); cgb.CreateCommand(Module.Prefix + "osu b") - .Description($"Shows information about an osu beatmap. |`{Prefix}osu b` https://osu.ppy.sh/s/127712`") + .Description($"Shows information about an osu beatmap. |`{Prefix}osu b` https://osu.ppy.sh/s/127712") .Parameter("map", ParameterType.Unparsed) .Do(async e => { @@ -88,7 +88,7 @@ namespace NadekoBot.Modules.Searches.Commands }); cgb.CreateCommand(Module.Prefix + "osu top5") - .Description($"Displays a user's top 5 plays. |{Prefix}osu top5 Name") + .Description($"Displays a user's top 5 plays. |`{Prefix}osu top5 Name`") .Parameter("usr", ParameterType.Required) .Parameter("mode", ParameterType.Unparsed) .Do(async e => diff --git a/NadekoBot/Modules/Searches/SearchesModule.cs b/NadekoBot/Modules/Searches/SearchesModule.cs index c484fabf..2662107a 100644 --- a/NadekoBot/Modules/Searches/SearchesModule.cs +++ b/NadekoBot/Modules/Searches/SearchesModule.cs @@ -86,7 +86,7 @@ $@"šŸŒ **Weather for** 怐{obj["target"]}怑 cgb.CreateCommand(Prefix + "ani") .Alias(Prefix + "anime", Prefix + "aq") .Parameter("query", ParameterType.Unparsed) - .Description($"Queries anilist for an anime and shows the first result. | `{Prefix}aq aquerion evol`") + .Description($"Queries anilist for an anime and shows the first result. | `{Prefix}aq aquarion evol`") .Do(async e => { if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; @@ -106,7 +106,7 @@ $@"šŸŒ **Weather for** 怐{obj["target"]}怑 cgb.CreateCommand(Prefix + "imdb") .Parameter("query", ParameterType.Unparsed) - .Description($"Queries imdb for movies or series, show first result. | `{Prefix}imdb query`") + .Description($"Queries imdb for movies or series, show first result. | `{Prefix}imdb Batman vs Superman`") .Do(async e => { if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; @@ -130,7 +130,7 @@ $@"šŸŒ **Weather for** 怐{obj["target"]}怑 cgb.CreateCommand(Prefix + "mang") .Alias(Prefix + "manga").Alias(Prefix + "mq") .Parameter("query", ParameterType.Unparsed) - .Description($"Queries anilist for a manga and shows the first result. | `{Prefix}mq query`") + .Description($"Queries anilist for a manga and shows the first result. | `{Prefix}mq Shingeki no kyojin`") .Do(async e => { if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")).ConfigureAwait(false))) return; @@ -149,7 +149,7 @@ $@"šŸŒ **Weather for** 怐{obj["target"]}怑 cgb.CreateCommand(Prefix + "randomcat") .Alias(Prefix + "meow") - .Description("Shows a random cat image. | `{Prefix}meow`") + .Description($"Shows a random cat image. | `{Prefix}meow`") .Do(async e => { await e.Channel.SendMessage(JObject.Parse( @@ -159,7 +159,7 @@ $@"šŸŒ **Weather for** 怐{obj["target"]}怑 cgb.CreateCommand(Prefix + "randomdog") .Alias(Prefix + "woof") - .Description("Shows a random dog image. | `{Prefix}woof`") + .Description($"Shows a random dog image. | `{Prefix}woof`") .Do(async e => { await e.Channel.SendMessage("http://random.dog/" + await SearchHelper.GetResponseStringAsync("http://random.dog/woof").ConfigureAwait(false)).ConfigureAwait(false); @@ -502,7 +502,7 @@ $@"šŸŒ **Weather for** 怐{obj["target"]}怑 cgb.CreateCommand(Prefix + "av") .Alias(Prefix + "avatar") .Parameter("mention", ParameterType.Required) - .Description($"Shows a mentioned person's avatar. | `{Prefix}av @X`") + .Description($"Shows a mentioned person's avatar. | `{Prefix}av \"@SomeGuy\"`") .Do(async e => { var usr = e.Channel.FindUsers(e.GetArg("mention")).FirstOrDefault(); diff --git a/NadekoBot/Modules/Trello/TrelloModule.cs b/NadekoBot/Modules/Trello/TrelloModule.cs index 527a2ed5..176ca2b8 100644 --- a/NadekoBot/Modules/Trello/TrelloModule.cs +++ b/NadekoBot/Modules/Trello/TrelloModule.cs @@ -71,7 +71,7 @@ namespace NadekoBot.Modules.Trello cgb.CreateCommand(Prefix + "bind") .Description("Bind a trello bot to a single channel. " + "You will receive notifications from your board when something is added or edited." + - $" | `{Prefix}bind [board_id]`") + $" **Bot Owner Only!**| `{Prefix}bind [board_id]`") .Parameter("board_id", Discord.Commands.ParameterType.Required) .Do(async e => { @@ -92,7 +92,7 @@ namespace NadekoBot.Modules.Trello }); cgb.CreateCommand(Prefix + "unbind") - .Description($"Unbinds a bot from the channel and board. | `{Prefix}unbind`") + .Description($"Unbinds a bot from the channel and board. **Bot Owner Only!**| `{Prefix}unbind`") .Do(async e => { if (!NadekoBot.IsOwner(e.User.Id)) return; @@ -106,7 +106,7 @@ namespace NadekoBot.Modules.Trello cgb.CreateCommand(Prefix + "lists") .Alias(Prefix + "list") - .Description($"Lists all lists, yo ;) | `{Prefix}list`") + .Description($"Lists all lists, yo ;) **Bot Owner Only!**| `{Prefix}list`") .Do(async e => { if (!NadekoBot.IsOwner(e.User.Id)) return; @@ -116,7 +116,7 @@ namespace NadekoBot.Modules.Trello }); cgb.CreateCommand(Prefix + "cards") - .Description($"Lists all cards from the supplied list. You can supply either a name or an index. | `{Prefix}cards index`") + .Description($"Lists all cards from the supplied list. You can supply either a name or an index. **Bot Owner Only!**| `{Prefix}cards index`") .Parameter("list_name", Discord.Commands.ParameterType.Unparsed) .Do(async e => { diff --git a/NadekoBot/Modules/Utility/UtilityModule.cs b/NadekoBot/Modules/Utility/UtilityModule.cs index 6fd8db53..218ccbae 100644 --- a/NadekoBot/Modules/Utility/UtilityModule.cs +++ b/NadekoBot/Modules/Utility/UtilityModule.cs @@ -48,7 +48,7 @@ namespace NadekoBot.Modules.Utility if (arr.Length == 0) await e.Channel.SendMessage("Nobody. (not 100% sure)").ConfigureAwait(false); else - await e.Channel.SendMessage("```xl\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Concat(ig.Select(el => $"• {el,-35}")))) + "\n```").ConfigureAwait(false); + await e.Channel.SendMessage("```xl\n" + string.Join("\n", arr.GroupBy(item => (i++) / 3).Select(ig => string.Concat(ig.Select(el => $"ā€¢ {el,-35}")))) + "\n```").ConfigureAwait(false); }); cgb.CreateCommand(Prefix + "inrole") @@ -134,7 +134,7 @@ namespace NadekoBot.Modules.Utility .Do(async e => await e.Channel.SendMessage("This server's ID is " + e.Server.Id).ConfigureAwait(false)); cgb.CreateCommand(Prefix + "roles") - .Description("List all roles on this server or a single user if specified.") + .Description($"List all roles on this server or a single user if specified. | `{Prefix}roles`") .Parameter("user", ParameterType.Unparsed) .Do(async e => { @@ -143,10 +143,10 @@ namespace NadekoBot.Modules.Utility var usr = e.Server.FindUsers(e.GetArg("user")).FirstOrDefault(); if (usr == null) return; - await e.Channel.SendMessage($"`List of roles for **{usr.Name}**:` \n• " + string.Join("\n• ", usr.Roles)).ConfigureAwait(false); + await e.Channel.SendMessage($"`List of roles for **{usr.Name}**:` \nā€¢ " + string.Join("\nā€¢ ", usr.Roles)).ConfigureAwait(false); return; } - await e.Channel.SendMessage("`List of roles:` \n• " + string.Join("\n• ", e.Server.Roles)).ConfigureAwait(false); + await e.Channel.SendMessage("`List of roles:` \nā€¢ " + string.Join("\nā€¢ ", e.Server.Roles)).ConfigureAwait(false); }); diff --git a/NadekoBot/NadekoBot.cs b/NadekoBot/NadekoBot.cs index a1299d97..b706b95d 100644 --- a/NadekoBot/NadekoBot.cs +++ b/NadekoBot/NadekoBot.cs @@ -121,8 +121,8 @@ namespace NadekoBot LogLevel = LogSeverity.Warning, LogHandler = (s, e) => Console.WriteLine($"Severity: {e.Severity}" + - $"Message: {e.Message}" + - $"ExceptionMessage: {e.Exception?.Message ?? "-"}"), + $"ExceptionMessage: {e.Exception?.Message ?? "-"}" + + $"Message: {e.Message}"), }); //create a command service @@ -197,7 +197,7 @@ namespace NadekoBot return; } #if NADEKO_RELEASE - await Task.Delay(120000).ConfigureAwait(false); + await Task.Delay(150000).ConfigureAwait(false); #else await Task.Delay(1000).ConfigureAwait(false); #endif diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj index d332dad2..341f1850 100644 --- a/NadekoBot/NadekoBot.csproj +++ b/NadekoBot/NadekoBot.csproj @@ -161,22 +161,6 @@ False lib\ScaredFingers.UnitsConversion.dll - - ..\packages\sqlite-net-pcl.1.1.2\lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLite-net.dll - True - - - ..\packages\SQLitePCL.bundle_green.0.9.2\lib\net45\SQLitePCL.batteries.dll - True - - - ..\packages\SQLitePCL.raw.0.9.2\lib\net45\SQLitePCL.raw.dll - True - - - ..\packages\SQLitePCL.plugin.sqlite3.net45.0.9.2\lib\net45\SQLitePCLPlugin_esqlite3.dll - True - @@ -217,6 +201,7 @@ + @@ -231,7 +216,7 @@ - + diff --git a/NadekoBot/SQLite.cs b/NadekoBot/SQLite.cs new file mode 100644 index 00000000..e74447f6 --- /dev/null +++ b/NadekoBot/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/NadekoBot/_Models/JSONModels/Configuration.cs b/NadekoBot/_Models/JSONModels/Configuration.cs index dbe8455b..04a4895e 100644 --- a/NadekoBot/_Models/JSONModels/Configuration.cs +++ b/NadekoBot/_Models/JSONModels/Configuration.cs @@ -81,6 +81,15 @@ namespace NadekoBot.Classes.JSONModels "https://cdn.discordapp.com/attachments/140007341880901632/156721724430352385/okawari_01_haruka_weird_mask.jpg", "https://cdn.discordapp.com/attachments/140007341880901632/156721728763068417/mustache-best-girl.png" + } }, + {"%mention% inv", new List() { + "To invite your bot, click on this link -> " + } }, + { "%mention% threaten", new List() { + "You wanna die, %target%?" + } }, + { "%mention% archer", new List() { + "http://i.imgur.com/Bha9NhL.jpg" } } }; @@ -208,7 +217,8 @@ Nadeko Support Server: "; { File.WriteAllText("data/config.json", JsonConvert.SerializeObject(NadekoBot.Config, Formatting.Indented)); } - finally { + finally + { configLock.Release(); } } diff --git a/NadekoBot/bin/Debug/data/config_example.json b/NadekoBot/bin/Debug/data/config_example.json index 9d6995c2..446ce36e 100644 --- a/NadekoBot/bin/Debug/data/config_example.json +++ b/NadekoBot/bin/Debug/data/config_example.json @@ -82,6 +82,15 @@ "https://cdn.discordapp.com/attachments/140007341880901632/156721715831898113/hqdefault.jpg", "https://cdn.discordapp.com/attachments/140007341880901632/156721724430352385/okawari_01_haruka_weird_mask.jpg", "https://cdn.discordapp.com/attachments/140007341880901632/156721728763068417/mustache-best-girl.png" + ], + "%mention% inv": [ + "To invite your bot, click on this link -> " + ], + "%mention% threaten": [ + "You wanna die, %target%?" + ], + "%mention% archer": [ + "http://i.imgur.com/Bha9NhL.jpg" ] }, "RotatingStatuses": [], diff --git a/NadekoBot/bin/Debug/x64/esqlite3.dll b/NadekoBot/bin/Debug/x64/esqlite3.dll deleted file mode 100644 index dd396d02..00000000 Binary files a/NadekoBot/bin/Debug/x64/esqlite3.dll and /dev/null differ diff --git a/NadekoBot/bin/Debug/x86/esqlite3.dll b/NadekoBot/bin/Debug/x86/esqlite3.dll deleted file mode 100644 index e5e53ff7..00000000 Binary files a/NadekoBot/bin/Debug/x86/esqlite3.dll and /dev/null differ diff --git a/NadekoBot/packages.config b/NadekoBot/packages.config index b59d511f..e41bec50 100644 --- a/NadekoBot/packages.config +++ b/NadekoBot/packages.config @@ -10,7 +10,7 @@ - + diff --git a/README.md b/README.md index 4992761a..7f57978b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ ![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true) [![Discord](https://discordapp.com/api/servers/117523346618318850/widget.png)](https://discord.gg/0ehQwTK2RBjAxzEY) +[![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/latest/?badge=latest) # NadekoBot -## [Click here to invite nadeko to your discord server](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) +## [Click here to invite Nadeko to your Discord server](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303) ## [Click here for a list of commands](https://github.com/Kwoth/NadekoBot/blob/master/commandlist.md) ## INSTRUCTIONS, FAQ ---> [Wiki](https://github.com/Kwoth/NadekoBot/wiki) diff --git a/commandlist.md b/commandlist.md index fd405bd1..4d09a425 100644 --- a/commandlist.md +++ b/commandlist.md @@ -1,82 +1,82 @@ -######For more information and how to setup your own NadekoBot, go to: **http://github.com/Kwoth/NadekoBot/** -######You can donate on patreon: `https://patreon.com/nadekobot` +######For more information and how to setup your own NadekoBot, go to: +######You can donate on patreon: ######or paypal: `nadekodiscordbot@gmail.com` -#NadekoBot List Of Commands -Version: `NadekoBot v0.9.6054.4837` +#NadekoBot List Of Commands ### Help Command and aliases | Description | Usage ----------------|--------------|------- `-h`, `-help`, `@BotName help`, `@BotName h`, `~h` | Either shows a help for a single command, or PMs you help link if no arguments are specified. | `-h !m q` or just `-h` `-hgit` | Generates the commandlist.md file. **Bot Owner Only!** | `-hgit` `-readme`, `-guide` | Sends a readme and a guide links to the channel. | `-readme` or `-guide` -`-donate`, `~donate` | Instructions for helping the project! | `{Prefix}donate` or `~donate` -`-modules`, `.modules` | List all bot modules. | `{Prefix}modules` or `.modules` -`-commands`, `.commands` | List all of the bot's commands from a certain module. | `{Prefix}commands` or `.commands` +`-donate`, `~donate` | Instructions for helping the project! | `-donate` or `~donate` +`-modules`, `.modules` | List all bot modules. | `-modules` or `.modules` +`-commands`, `.commands` | List all of the bot's commands from a certain module. | `-commands` or `.commands` ### Administration Command and aliases | Description | Usage ----------------|--------------|------- -`.grdel` | Toggles automatic deletion of greet and bye messages. | `.grdel` -`.greet` | Toggles anouncements on the current channel when someone joins the server. | `.greet` -`.greetmsg` | Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. | `.greetmsg Welcome to the server, %user%.` +`.grdel` | Toggles automatic deletion of greet and bye messages. **Needs Manage Server Permissions.**| `.grdel` +`.greet` | Toggles anouncements on the current channel when someone joins the server. **Needs Manage Server Permissions.**| `.greet` +`.greetmsg` | Sets a new join announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current greet message. **Needs Manage Server Permissions.**| `.greetmsg Welcome to the server, %user%.` `.bye` | Toggles anouncements on the current channel when someone leaves the server. | `.bye` -`.byemsg` | Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. | `.byemsg %user% has left the server.` -`.byepm` | Toggles whether the good bye messages will be sent in a PM or in the text channel. | `.byepm` -`.greetpm` | Toggles whether the greet messages will be sent in a PM or in the text channel. | `.greetpm` -`.spmom` | Toggles whether mentions of other offline users on your server will send a pm to them. | `.spmom` +`.byemsg` | Sets a new leave announcement message. Type %user% if you want to mention the new member. Using it with no message will show the current bye message. **Needs Manage Server Permissions.**| `.byemsg %user% has left the server.` +`.byepm` | Toggles whether the good bye messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `.byepm` +`.greetpm` | Toggles whether the greet messages will be sent in a PM or in the text channel. **Needs Manage Server Permissions.**| `.greetpm` +`.spmom` | Toggles whether mentions of other offline users on your server will send a pm to them. **Needs Manage Server Permissions.**| `.spmom` `.logserver` | Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!** | `.logserver` -`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. | `.logignore` -`.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. | `.userpresence` -`.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. | `{Prefix}voicerpresence` -`.repeatinvoke`, `.repinv` | Immediately shows the repeat message and restarts the timer. | `{Prefix}repinv` -`.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. | `.ropl` -`.addplaying`, `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %trivia% | `.adpl` -`.listplaying`, `.lipl` | Lists all playing statuses with their corresponding number. | `.lipl` -`.removeplaying`, `.repl`, `.rmpl` | Removes a playing string on a given number. | `.rmpl` -`.slowmode` | Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. | `.slowmode` -`.cleanv+t`, `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk.** | `.cleanv+t` -`.voice+text`, `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. | `.voice+text` -`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. | `.scsc` -`.jcsc` | Joins current channel to an instance of cross server channel using the token. | `.jcsc` -`.lcsc` | Leaves Cross server channel instance from this channel. | `.lcsc` -`.asar` | Adds a role, or list of roles separated by whitespace(use quotations for multiword roles) to the list of self-assignable roles. | .asar Gamer +`.logignore` | Toggles whether the .logserver command ignores this channel. Useful if you have hidden admin channel and public log channel. **Bot Owner Only!**| `.logignore` +`.userpresence` | Starts logging to this channel when someone from the server goes online/offline/idle. **Needs Manage Server Permissions.**| `.userpresence` +`.voicepresence` | Toggles logging to this channel whenever someone joins or leaves a voice channel you are in right now. **Needs Manage Server Permissions.**| `.voicerpresence` +`.repeatinvoke`, `.repinv` | Immediately shows the repeat message and restarts the timer. **Needs Manage Messages Permissions.**| `.repinv` +`.repeat` | Repeat a message every X minutes. If no parameters are specified, repeat is disabled. **Needs Manage Messages Permissions.** | `.repeat 5 Hello there` +`.rotateplaying`, `.ropl` | Toggles rotation of playing status of the dynamic strings you specified earlier. **Bot Owner Only!** | `.ropl` +`.addplaying`, `.adpl` | Adds a specified string to the list of playing strings to rotate. Supported placeholders: %servers%, %users%, %playing%, %queued%, %trivia% **Bot Owner Only!**| `.adpl` +`.listplaying`, `.lipl` | Lists all playing statuses with their corresponding number. **Bot Owner Only!**| `.lipl` +`.removeplaying`, `.repl`, `.rmpl` | Removes a playing string on a given number. **Bot Owner Only!**| `.rmpl` +`.slowmode` | Toggles slow mode. When ON, users will be able to send only 1 message every 5 seconds. **Needs Manage Messages Permissions.**| `.slowmode` +`.cleanv+t`, `.cv+t` | Deletes all text channels ending in `-voice` for which voicechannels are not found. **Use at your own risk. +Needs Manage Roles and Manage Channels Permissions.** | `.cleanv+t` +`.voice+text`, `.v+t` | Creates a text channel for each voice channel only users in that voice channel can see.If you are server owner, keep in mind you will see them all the time regardless. **Needs Manage Roles and Manage Channels Permissions.**| `.voice+text` +`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. **Bot Owner Only.** | `.scsc` +`.jcsc` | Joins current channel to an instance of cross server channel using the token. **Needs Manage Server Permissions.**| `.jcsc` +`.lcsc` | Leaves Cross server channel instance from this channel. **Needs Manage Server Permissions.**| `.lcsc` +`.asar` | Adds a role, or list of roles separated by whitespace(use quotations for multiword roles) to the list of self-assignable roles. **Needs Manage Roles Permissions.**| `.asar Gamer` `.rsar` | Removes a specified role from the list of self-assignable roles. | `.rsar` `.lsar` | Lists all self-assignable roles. | `.lsar` `.togglexclsar`, `.tesar` | toggle whether the self-assigned roles should be exclusive | `.tesar` -`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | .iam Gamer -`.iamnot`, `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | .iamn Gamer +`.iam` | Adds a role to you that you choose. Role must be on a list of self-assignable roles. | `.iam Gamer` +`.iamnot`, `.iamn` | Removes a role to you that you choose. Role must be on a list of self-assignable roles. | `.iamn Gamer` `.addcustreact`, `.acr` | Add a custom reaction. Guide here: **Bot Owner Only!** | `.acr "hello" I love saying hello to %user%` `.listcustreact`, `.lcr` | Lists custom reactions (paginated with 30 commands per page). Use 'all' instead of page number to get all custom reactions DM-ed to you. | `.lcr 1` `.showcustreact`, `.scr` | Shows all possible responses from a single custom reaction. | `.scr %mention% bb` `.editcustreact`, `.ecr` | Edits a custom reaction, arguments are custom reactions name, index to change, and a (multiword) message **Bot Owner Only** | `.ecr "%mention% disguise" 2 Test 123` -`.delcustreact`, `.dcr` | Deletes a custom reaction with given name (and index). | `.dcr index` -`.autoassignrole`, `.aar` | Automaticaly assigns a specified role to every user who joins the server. Type `.aar` to disable, `.aar Role Name` to enable -`.leave` | Makes Nadeko leave the server. Either name or id required. | `.leave 123123123331` -`.listincidents`, `.lin` | List all UNREAD incidents and flags them as read. | `.lin` -`.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. | `.lain` -`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. Server Manager Only. | `.delmsgoncmd` +`.delcustreact`, `.dcr` | Deletes a custom reaction with given name (and index). **Bot Owner Only.**| `.dcr index` +`.autoassignrole`, `.aar` | Automaticaly assigns a specified role to every user who joins the server. **Needs Manage Roles Permissions.** | `.aar` to disable, `.aar Role Name` to enable +`.leave` | Makes Nadeko leave the server. Either name or id required. **Bot Owner Only!**| `.leave 123123123331` +`.listincidents`, `.lin` | List all UNREAD incidents and flags them as read. **Needs Manage Server Permissions.**| `.lin` +`.listallincidents`, `.lain` | Sends you a file containing all incidents and flags them as read. **Needs Manage Server Permissions.**| `.lain` +`.delmsgoncmd` | Toggles the automatic deletion of user's successful command message to prevent chat flood. **Server Manager Only.** | `.delmsgoncmd` `.restart` | Restarts the bot. Might not work. **Bot Owner Only** | `.restart` -`.setrole`, `.sr` | Sets a role for a given user. | `.sr @User Guest` -`.removerole`, `.rr` | Removes a role from a given user. | `.rr @User Admin` -`.renamerole`, `.renr` | Renames a role. Role you are renaming must be lower than bot's highest role. | `.renr "First role" SecondRole` -`.removeallroles`, `.rar` | Removes all roles from a mentioned user. | `.rar @User` -`.createrole`, `.cr` | Creates a role with a given name. | `.cr Awesome Role` -`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. | `.rc Admin 255 200 100` or `.rc Admin ffba55` -`.ban`, `.b` | Bans a user by id or name with an optional message. | `.b "@some Guy" Your behaviour is toxic.` -`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. | `.sb "@some Guy" Your behaviour is toxic.` -`.kick`, `.k` | Kicks a mentioned user. | `.k "@some Guy" Your behaviour is toxic.` -`.mute` | Mutes mentioned user or users. | `.mute "@Someguy"` or `.mute "@Someguy" "@Someguy"` -`.unmute` | Unmutes mentioned user or users. | `.unmute "@Someguy"` or `.unmute "@Someguy" "@Someguy"` -`.deafen`, `.deaf` | Deafens mentioned user or users | `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"` -`.undeafen`, `.undef` | Undeafens mentioned user or users | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"` -`.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. | `.dvch VoiceChannelName` -`.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. | `.cvch VoiceChannelName` -`.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. | `.dtch TextChannelName` -`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. | `.ctch TextChannelName` -`.settopic`, `.st` | Sets a topic on the current channel. | `.st My new topic` -`.setchanlname`, `.schn` | Changed the name of the current channel.| `.schn NewName` +`.setrole`, `.sr` | Sets a role for a given user. **Needs Manage Roles Permissions.**| `.sr @User Guest` +`.removerole`, `.rr` | Removes a role from a given user. **Needs Manage Roles Permissions.**| `.rr @User Admin` +`.renamerole`, `.renr` | Renames a role. Roles you are renaming must be lower than bot's highest role. **Manage Roles Permissions.** | `.renr "First role" SecondRole` +`.removeallroles`, `.rar` | Removes all roles from a mentioned user. **Needs Manage Roles Permissions.**| `.rar @User` +`.createrole`, `.cr` | Creates a role with a given name. **Needs Manage Roles Permissions.**| `.cr Awesome Role` +`.rolecolor`, `.rc` | Set a role's color to the hex or 0-255 rgb color value provided. **Needs Manage Roles Permissions.** | `.rc Admin 255 200 100` or `.rc Admin ffba55` +`.ban`, `.b` | Bans a user by id or name with an optional message. **Needs Ban Permissions.**| `.b "@some Guy" Your behaviour is toxic.` +`.softban`, `.sb` | Bans and then unbans a user by id or name with an optional message. **Needs Ban Permissions.**| `.sb "@some Guy" Your behaviour is toxic.` +`.kick`, `.k` | Kicks a mentioned user. **Needs Kick Permissions.**| `.k "@some Guy" Your behaviour is toxic.` +`.mute` | Mutes mentioned user or users. **Needs Mute Permissions.**| `.mute "@Someguy"` or `.mute "@Someguy" "@Someguy"` +`.unmute` | Unmutes mentioned user or users. **Needs Mute Permissions.**| `.unmute "@Someguy"` or `.unmute "@Someguy" "@Someguy"` +`.deafen`, `.deaf` | Deafens mentioned user or users. **Needs Deafen Permissions.**| `.deaf "@Someguy"` or `.deaf "@Someguy" "@Someguy"` +`.undeafen`, `.undef` | Undeafens mentioned user or users. **Needs Deafen Permissions.** | `.undef "@Someguy"` or `.undef "@Someguy" "@Someguy"` +`.delvoichanl`, `.dvch` | Deletes a voice channel with a given name. **Needs Manage Channel Permissions.**| `.dvch VoiceChannelName` +`.creatvoichanl`, `.cvch` | Creates a new voice channel with a given name. **Needs Manage Channel Permissions.** | `.cvch VoiceChannelName` +`.deltxtchanl`, `.dtch` | Deletes a text channel with a given name. **Needs Manage Channel Permissions.** | `.dtch TextChannelName` +`.creatxtchanl`, `.ctch` | Creates a new text channel with a given name. **Needs Manage Channel Permissions.** | `.ctch TextChannelName` +`.settopic`, `.st` | Sets a topic on the current channel. **Needs Manage Channel Permissions.** | `.st My new topic` +`.setchanlname`, `.schn` | Changed the name of the current channel. **Needs Manage Channel Permissions.**| `.schn NewName` `.heap` | Shows allocated memory - **Bot Owner Only!** | `.heap` `.prune`, `.clr` | `.prune` removes all nadeko's messages in the last 100 messages.`.prune X` removes last X messages from the channel (up to 100)`.prune @Someone` removes all Someone's messages in the last 100 messages.`.prune @Someone X` removes last X 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X` `.die` | Shuts the bot down and notifies users about the restart. **Bot Owner Only!** | `.die` @@ -86,8 +86,8 @@ Command and aliases | Description | Usage `.send` | Send a message to someone on a different server through the bot. **Bot Owner Only!** | `.send serverid|u:user_id Send this to a user!` or `.send serverid|c:channel_id Send this to a channel!` `.mentionrole`, `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. Requires you to have mention everyone permission. | `.menro RoleName` `.unstuck` | Clears the message queue. **Bot Owner Only!** | `.unstuck` -`.donators` | List of lovely people who donated to keep this project alive. -`.donadd` | Add a donator to the database. | `.donadd Donate Amount` +`.donators` | List of lovely people who donated to keep this project alive. | `.donators` +`.donadd` | Add a donator to the database. **Kwoth Only** | `.donadd Donate Amount` `.announce` | Sends a message to all servers' general channel bot is connected to.**Bot Owner Only!** | `.announce Useless spam` `.savechat` | Saves a number of messages to a text file and sends it to you. **Bot Owner Only** | `.savechat 150` @@ -107,19 +107,19 @@ Command and aliases | Description | Usage `.userid`, `.uid` | Shows user ID. | `.uid` or `.uid "@SomeGuy"` `.channelid`, `.cid` | Shows current channel ID. | `.cid` `.serverid`, `.sid` | Shows current server ID. | `.sid` -`.roles` | List all roles on this server or a single user if specified. +`.roles` | List all roles on this server or a single user if specified. | `.roles` `.channeltopic`, `.ct` | Sends current channel's topic as a message. | `.ct` ### Permissions Command and aliases | Description | Usage ----------------|--------------|------- -`;chnlfilterinv`, `;cfi` | Enables or disables automatic deleting of invites on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | ;cfi enable #general-chat -`;srvrfilterinv`, `;sfi` | Enables or disables automatic deleting of invites on the server. | ;sfi disable -`;chnlfilterwords`, `;cfw` | Enables or disables automatic deleting of messages containing banned words on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | ;cfw enable #general-chat -`;addfilterword`, `;afw` | Adds a new word to the list of filtered words | ;afw poop -`;rmvfilterword`, `;rfw` | Removes the word from the list of filtered words | ;rw poop -`;lstfilterwords`, `;lfw` | Shows a list of filtered words | ;lfw -`;srvrfilterwords`, `;sfw` | Enables or disables automatic deleting of messages containing forbidden words on the server. | ;sfw disable +`;chnlfilterinv`, `;cfi` | Enables or disables automatic deleting of invites on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | `;cfi enable #general-chat` +`;srvrfilterinv`, `;sfi` | Enables or disables automatic deleting of invites on the server. | `;sfi disable` +`;chnlfilterwords`, `;cfw` | Enables or disables automatic deleting of messages containing banned words on the channel.If no channel supplied, it will default to current one. Use ALL to apply to all existing channels at once. | `;cfw enable #general-chat` +`;addfilterword`, `;afw` | Adds a new word to the list of filtered words | `;afw poop` +`;rmvfilterword`, `;rfw` | Removes the word from the list of filtered words | `;rw poop` +`;lstfilterwords`, `;lfw` | Shows a list of filtered words | `;lfw` +`;srvrfilterwords`, `;sfw` | Enables or disables automatic deleting of messages containing forbidden words on the server. | `;sfw disable` `;permrole`, `;pr` | Sets a role which can change permissions. Or supply no parameters to find out the current one. Default one is 'Nadeko'. | `;pr role` `;rolepermscopy`, `;rpc` | Copies BOT PERMISSIONS (not discord permissions) from one role to another. | `;rpc Some Role ~ Some other role` `;chnlpermscopy`, `;cpc` | Copies BOT PERMISSIONS (not discord permissions) from one channel to another. | `;cpc Some Channel ~ Some other channel` @@ -149,7 +149,7 @@ Command and aliases | Description | Usage `;cubl` | Unblacklists a mentioned channel (#general for example). | `;cubl #some_channel` `;sbl` | Blacklists a server by a name or id (#general for example). **BOT OWNER ONLY** | `;sbl [servername/serverid]` `;cmdcooldown`, `;cmdcd` | Sets a cooldown per user for a command. Set 0 to clear. | `;cmdcd "some cmd" 5` -`;allcmdcooldowns`, `;acmdcds` | Shows a list of all commands and their respective cooldowns. +`;allcmdcooldowns`, `;acmdcds` | Shows a list of all commands and their respective cooldowns. | `;acmdcds` ### Conversations Command and aliases | Description | Usage @@ -157,7 +157,7 @@ Command and aliases | Description | Usage `..` | Adds a new quote with the specified name (single word) and message (no limit). | `.. abc My message` `...` | Shows a random quote with a specified name. | `... abc` `..qdel`, `..quotedelete` | Deletes all quotes with the specified keyword. You have to either be bot owner or the creator of the quote to delete it. | `..qdel abc` -`@BotName rip` | Shows a grave image of someone with a start year | @NadekoBot rip @Someone 2000 +`@BotName rip` | Shows a grave image of someone with a start year | `@NadekoBot rip @Someone 2000` `@BotName die` | Works only for the owner. Shuts the bot down. | `@NadekoBot die` `@BotName do you love me` | Replies with positive answer only to the bot owner. | `@NadekoBot do you love me` `@BotName how are you`, `@BotName how are you?` | Replies positive only if bot owner is online. | `@NadekoBot how are you` @@ -183,7 +183,7 @@ Command and aliases | Description | Usage `$award` | Gives someone a certain amount of flowers. **Bot Owner Only!** | `$award 100 @person` `$take` | Takes a certain amount of flowers from someone. **Bot Owner Only!** | `$take 1 "@someguy"` `$betroll`, `$br` | Bets a certain amount of NadekoFlowers and rolls a dice. Rolling over 66 yields x2 flowers, over 90 - x3 and 100 x10. | `$br 5` -`$leaderboard`, `$lb` | Displays bot currency leaderboard | $lb +`$leaderboard`, `$lb` | Displays bot currency leaderboard | `$lb` ### Games Command and aliases | Description | Usage @@ -239,8 +239,8 @@ Command and aliases | Description | Usage `!!playlists`, `!!pls` | Lists all playlists. Paginated. 20 per page. Default page is 0. | `!!pls 1` `!!deleteplaylist`, `!!delpls` | Deletes a saved playlist. Only if you made it or if you are the bot owner. | `!!delpls animu-5` `!!goto` | Goes to a specific time in seconds in a song. | `!!goto 30` -`!!getlink`, `!!gl` | Shows a link to the currently playing song. -`!!autoplay`, `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) +`!!getlink`, `!!gl` | Shows a link to the song in the queue by index, or the currently playing song by default. | `!!gl` +`!!autoplay`, `!!ap` | Toggles autoplay - When the song is finished, automatically queue a related youtube song. (Works only for youtube songs and when queue is empty) | `!!ap` ### Searches Command and aliases | Description | Usage @@ -258,21 +258,21 @@ Command and aliases | Description | Usage `~convert` | Convert quantities from>to. | `~convert m>km 1000` `~convertlist` | List of the convertable dimensions and currencies. `~wowjoke` | Get one of Kwoth's penultimate WoW jokes. | `~wowjoke` -`~calculate`, `~calc` | Evaluate a mathematical expression. | ~calc 1+1 +`~calculate`, `~calc` | Evaluate a mathematical expression. | `~calc 1+1` `~osu` | Shows osu stats for a player. | `~osu Name` or `~osu Name taiko` -`~osu b` | Shows information about an osu beatmap. | `~osu b` https://osu.ppy.sh/s/127712` -`~osu top5` | Displays a user's top 5 plays. | ~osu top5 Name +`~osu b` | Shows information about an osu beatmap. | `~osu b` https://osu.ppy.sh/s/127712 +`~osu top5` | Displays a user's top 5 plays. | `~osu top5 Name` `~pokemon`, `~poke` | Searches for a pokemon. | `~poke Sylveon` `~pokemonability`, `~pokeab` | Searches for a pokemon ability. | `~pokeab "water gun"` `~memelist` | Pulls a list of memes you can use with `~memegen` from http://memegen.link/templates/ | `~memelist` `~memegen` | Generates a meme from memelist with top and bottom text. | `~memegen biw "gets iced coffee" "in the winter"` `~we` | Shows weather data for a specified city and a country. BOTH ARE REQUIRED. Use country abbrevations. | `~we Moscow RF` `~yt` | Searches youtubes and shows the first result | `~yt query` -`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. | `~aq aquerion evol` -`~imdb` | Queries imdb for movies or series, show first result. | `~imdb query` -`~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. | `~mq query` -`~randomcat`, `~meow` | Shows a random cat image. -`~randomdog`, `~woof` | Shows a random dog image. +`~ani`, `~anime`, `~aq` | Queries anilist for an anime and shows the first result. | `~aq aquarion evol` +`~imdb` | Queries imdb for movies or series, show first result. | `~imdb Batman vs Superman` +`~mang`, `~manga`, `~mq` | Queries anilist for a manga and shows the first result. | `~mq Shingeki no kyojin` +`~randomcat`, `~meow` | Shows a random cat image. | `~meow` +`~randomdog`, `~woof` | Shows a random dog image. | `~woof` `~i` | Pulls the first image found using a search parameter. Use ~ir for different results. | `~i cute kitten` `~ir` | Pulls a random image using a search parameter. | `~ir cute kitten` `~lmgtfy` | Google something for an idiot. | `~lmgtfy query` @@ -292,7 +292,7 @@ Command and aliases | Description | Usage `~wiki` | Gives you back a wikipedia link | `~wiki query` `~clr` | Shows you what color corresponds to that hex. | `~clr 00ff00` `~videocall` | Creates a private video call link for you and other mentioned people. The link is sent to mentioned people via a private message. | `~videocall "@SomeGuy"` -`~av`, `~avatar` | Shows a mentioned person's avatar. | `~av @X` +`~av`, `~avatar` | Shows a mentioned person's avatar. | `~av "@SomeGuy"` ### NSFW Command and aliases | Description | Usage @@ -309,15 +309,15 @@ Command and aliases | Description | Usage ### ClashOfClans Command and aliases | Description | Usage ----------------|--------------|------- -`,createwar`, `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | ,cw 15 The Enemy Clan -`,startwar`, `,sw` | Starts a war with a given number. -`,listwar`, `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | ,lw [war_number] or ,lw -`,claim`, `,call`, `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | ,call [war_number] [base_number] [optional_other_name] -`,claimfinish`, `,cf`, `,cf3`, `,claimfinish3` | Finish your claim with 3 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] -`,claimfinish2`, `,cf2` | Finish your claim with 2 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] -`,claimfinish1`, `,cf1` | Finish your claim with 1 stars if you destroyed a base. Optional second argument finishes for someone else. | ,cf [war_number] [optional_other_name] -`,unclaim`, `,uncall`, `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | ,uc [war_number] [optional_other_name] -`,endwar`, `,ew` | Ends the war with a given index. | ,ew [war_number] +`,createwar`, `,cw` | Creates a new war by specifying a size (>10 and multiple of 5) and enemy clan name. | `,cw 15 The Enemy Clan` +`,startwar`, `,sw` | Starts a war with a given number. | `,sw 15` +`,listwar`, `,lw` | Shows the active war claims by a number. Shows all wars in a short way if no number is specified. | `,lw [war_number] or ,lw` +`,claim`, `,call`, `,c` | Claims a certain base from a certain war. You can supply a name in the third optional argument to claim in someone else's place. | `,call [war_number] [base_number] [optional_other_name]` +`,claimfinish`, `,cf`, `,cf3`, `,claimfinish3` | Finish your claim with 3 stars if you destroyed a base. Optional second argument finishes for someone else. | `,cf [war_number] [optional_other_name]` +`,claimfinish2`, `,cf2` | Finish your claim with 2 stars if you destroyed a base. Optional second argument finishes for someone else. | `,cf [war_number] [optional_other_name]` +`,claimfinish1`, `,cf1` | Finish your claim with 1 stars if you destroyed a base. Optional second argument finishes for someone else. | `,cf [war_number] [optional_other_name]` +`,unclaim`, `,uncall`, `,uc` | Removes your claim from a certain war. Optional second argument denotes a person in whose place to unclaim | `,uc [war_number] [optional_other_name]` +`,endwar`, `,ew` | Ends the war with a given index. | `,ew [war_number]` ### Pokegame Command and aliases | Description | Usage @@ -332,32 +332,34 @@ Command and aliases | Description | Usage Command and aliases | Description | Usage ----------------|--------------|------- `~translate`, `~trans` | Translates from>to text. From the given language to the destiation language. | `~trans en>fr Hello` -`~translangs` | List the valid languages for translation. | `{Prefix}translangs` or `{Prefix}translangs language` +`~translangs` | List the valid languages for translation. | `~translangs` or `~translangs language` ### Customreactions Command and aliases | Description | Usage ----------------|--------------|------- -`\o\` | Custom reaction. | \o\ -`/o/` | Custom reaction. | /o/ -`moveto` | Custom reaction. | moveto -`comeatmebro` | Custom reaction. | comeatmebro -`e` | Custom reaction. | e -`@BotName insult`, `<@!116275390695079945> insult` | Custom reaction. | %mention% insult -`@BotName praise`, `<@!116275390695079945> praise` | Custom reaction. | %mention% praise -`@BotName pat`, `<@!116275390695079945> pat` | Custom reaction. | %mention% pat -`@BotName cry`, `<@!116275390695079945> cry` | Custom reaction. | %mention% cry -`@BotName are you real?`, `<@!116275390695079945> are you real?` | Custom reaction. | %mention% are you real? -`@BotName are you there?`, `<@!116275390695079945> are you there?` | Custom reaction. | %mention% are you there? -`@BotName draw`, `<@!116275390695079945> draw` | Custom reaction. | %mention% draw -`@BotName bb`, `<@!116275390695079945> bb` | Custom reaction. | %mention% bb -`@BotName call`, `<@!116275390695079945> call` | Custom reaction. | %mention% call -`@BotName disguise`, `<@!116275390695079945> disguise` | Custom reaction. | %mention% disguise -`~hentai` | Custom reaction. | ~hentai +`\o\` | Custom reaction. | `\o\` +`/o/` | Custom reaction. | `/o/` +`moveto` | Custom reaction. | `moveto` +`comeatmebro` | Custom reaction. | `comeatmebro` +`e` | Custom reaction. | `e` +`@BotName insult`, `<@!116275390695079945> insult` | Custom reaction. | `%mention% insult` +`@BotName praise`, `<@!116275390695079945> praise` | Custom reaction. | `%mention% praise` +`@BotName pat`, `<@!116275390695079945> pat` | Custom reaction. | `%mention% pat` +`@BotName cry`, `<@!116275390695079945> cry` | Custom reaction. | `%mention% cry` +`@BotName are you real?`, `<@!116275390695079945> are you real?` | Custom reaction. | `%mention% are you real?` +`@BotName are you there?`, `<@!116275390695079945> are you there?` | Custom reaction. | `%mention% are you there?` +`@BotName draw`, `<@!116275390695079945> draw` | Custom reaction. | `%mention% draw` +`@BotName bb`, `<@!116275390695079945> bb` | Custom reaction. | `%mention% bb` +`@BotName call`, `<@!116275390695079945> call` | Custom reaction. | `%mention% call` +`@BotName disguise`, `<@!116275390695079945> disguise` | Custom reaction. | `%mention% disguise` +`@BotName inv`, `<@!116275390695079945> inv` | Custom reaction. | `%mention% inv` +`@BotName threaten`, `<@!116275390695079945> threaten` | Custom reaction. | `%mention% threaten` +`@BotName archer`, `<@!116275390695079945> archer` | Custom reaction. | `%mention% archer` ### Trello Command and aliases | Description | Usage ----------------|--------------|------- -`trello bind` | Bind a trello bot to a single channel. You will receive notifications from your board when something is added or edited. | `trello 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 index` +`trello bind` | Bind a trello bot to a single channel. You will receive notifications from your board when something is added or edited. **Bot Owner Only!**| `trello bind [board_id]` +`trello unbind` | Unbinds a bot from the channel and board. **Bot Owner Only!**| `trello unbind` +`trello lists`, `trello list` | Lists all lists, yo ;) **Bot Owner Only!**| `trello list` +`trello cards` | Lists all cards from the supplied list. You can supply either a name or an index. **Bot Owner Only!**| `trello cards index` diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..9cec30e0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1 @@ +Hai, this will be docs of nakeda \ No newline at end of file