Merge pull request #15 from Kwoth/1.4

ups
This commit is contained in:
samvaio 2017-07-13 17:14:29 +05:30 committed by GitHub
commit 4dbdce7f5d
174 changed files with 6511 additions and 3498 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
#Manually added files
patreon_rewards.json
command_errors*.txt
src/NadekoBot/Command Errors*.txt

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.6
VisualStudioVersion = 15.0.26430.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{04929013-5BAB-42B0-B9B2-8F2BB8F16AF2}"
EndProject

View File

@ -1,7 +1,7 @@
![img](https://ci.appveyor.com/api/projects/status/gmu6b3ltc80hr3k9?svg=true)
[![Discord](https://discordapp.com/api/guilds/117523346618318850/widget.png)](https://discord.gg/nadekobot)
[![Documentation Status](https://readthedocs.org/projects/nadekobot/badge/?version=latest)](http://nadekobot.readthedocs.io/en/latest/?badge=latest)
[![nadeko0](https://cdn.discordapp.com/attachments/266240393639755778/281920716809699328/part1.png)](http://nadekobot.xyz)
[![nadeko0](https://cdn.discordapp.com/attachments/266240393639755778/281920716809699328/part1.png)](https://nadekobot.me)
[![nadeko1](https://cdn.discordapp.com/attachments/266240393639755778/281920134967328768/part2.png)](https://discordapp.com/oauth2/authorize?client_id=170254782546575360&scope=bot&permissions=66186303)
[![nadeko2](https://cdn.discordapp.com/attachments/266240393639755778/281920161311883264/part3.png)](http://nadekobot.readthedocs.io/en/latest/Commands%20List/)

View File

@ -34,7 +34,6 @@ Commands and aliases | Description | Usage
`.creatxtchanl` `.ctch` | Creates a new text channel with a given name. **Requires ManageChannels server permission.** | `.ctch TextChannelName`
`.settopic` `.st` | Sets a topic on the current channel. **Requires ManageChannels server permission.** | `.st My new topic`
`.setchanlname` `.schn` | Changes the name of the current channel. **Requires ManageChannels server permission.** | `.schn NewName`
`.prune` `.clear` | `.prune` removes all Nadeko's messages in the last 100 messages. `.prune X` removes last `X` number of 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` number of 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`
`.mentionrole` `.menro` | Mentions every person from the provided role or roles (separated by a ',') on this server. **Requires MentionEveryone server permission.** | `.menro RoleName`
`.donators` | List of the lovely people who donated to keep this project alive. | `.donators`
`.donadd` | Add a donator to the database. **Bot owner only** | `.donadd Donate Amount`
@ -65,6 +64,7 @@ Commands and aliases | Description | Usage
`.antispam` | Stops people from repeating same message X times in a row. You can specify to either mute, kick or ban the offenders. Max message count is 10. **Requires Administrator server permission.** | `.antispam 3 Mute` or `.antispam 4 Kick` or `.antispam 6 Ban`
`.antispamignore` | Toggles whether antispam ignores current channel. Antispam must be enabled. | `.antispamignore`
`.antilist` `.antilst` | Shows currently enabled protection features. | `.antilist`
`.prune` `.clear` | `.prune` removes all Nadeko's messages in the last 100 messages. `.prune X` removes last `X` number of 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` number of 'Someone's' messages in the channel. | `.prune` or `.prune 5` or `.prune @Someone` or `.prune @Someone X`
`.slowmode` | Toggles slowmode. Disable by specifying no parameters. To enable, specify a number of messages each user can send, and an interval in seconds. For example 1 message every 5 seconds. **Requires ManageMessages server permission.** | `.slowmode 1 5` or `.slowmode`
`.slowmodewl` | Ignores a role or a user from the slowmode feature. **Requires ManageMessages server permission.** | `.slowmodewl SomeRole` or `.slowmodewl AdminDude`
`.adsarm` | Toggles the automatic deletion of confirmations for `.iam` and `.iamn` commands. **Requires ManageMessages server permission.** | `.adsarm`
@ -81,7 +81,6 @@ Commands and aliases | Description | Usage
`.scclr` | Removes all startup commands. **Bot owner only** | `.scclr`
`.fwmsgs` | Toggles forwarding of non-command messages sent to bot's DM to the bot owners **Bot owner only** | `.fwmsgs`
`.fwtoall` | Toggles whether messages will be forwarded to all bot owners or only to the first one specified in the credentials.json file **Bot owner only** | `.fwtoall`
`.connectshard` | Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors. **Bot owner only** | `.connectshard 2`
`.leave` | Makes Nadeko leave the server. Either server name or server ID is required. **Bot owner only** | `.leave 123123123331`
`.die` | Shuts the bot down. **Bot owner only** | `.die`
`.setname` `.newnm` | Gives the bot a new name. **Bot owner only** | `.newnm BotName`
@ -105,6 +104,7 @@ Commands and aliases | Description | Usage
`.timezone` | Sets this guilds timezone. This affects bot's time output in this server (logs, etc..) | `.timezone` or `.timezone GMT Standard Time`
`.warn` | Warns a user. **Requires BanMembers server permission.** | `.warn @b1nzy Very rude person`
`.warnlog` | See a list of warnings of a certain user. **Requires BanMembers server permission.** | `.warnlog @b1nzy`
`.warnlogall` | See a list of all warnings on the server. 15 users per page. **Requires BanMembers server permission.** | `.warnlogall` or `.warnlogall 2`
`.warnclear` `.warnc` | Clears all warnings from a certain user. **Requires BanMembers server permission.** | `.warnclear @PoorDude`
`.warnpunish` `.warnp` | Sets a punishment for a certain number of warnings. Provide no punishment to remove. **Requires BanMembers server permission.** | `.warnpunish 5 Ban` or `.warnpunish 3`
`.warnpunishlist` `.warnpl` | Lists punishments for warnings. | `.warnpunishlist`
@ -199,11 +199,11 @@ Commands and aliases | Description | Usage
`.cleverbot` | Toggles cleverbot session. When enabled, the bot will reply to messages starting with bot mention in the server. Custom reactions starting with %mention% won't work if cleverbot is enabled. **Requires ManageMessages server permission.** | `.cleverbot`
`.hangmanlist` | Shows a list of hangman term types. | `.hangmanlist`
`.hangman` | Starts a game of hangman in the channel. Use `.hangmanlist` to see a list of available term types. Defaults to 'all'. | `.hangman` or `.hangman movies`
`.hangmanstop` | Stops the active hangman game on this channel if it exists. | `.hangmanstop`
`.pick` | Picks the currency planted in this channel. 60 seconds cooldown. | `.pick`
`.plant` | Spend an amount of currency to plant it in this channel. Default is 1. (If bot is restarted or crashes, the currency will be lost) | `.plant` or `.plant 5`
`.gencurrency` `.gc` | Toggles currency generation on this channel. Every posted message will have chance to spawn currency. Chance is specified by the Bot Owner. (default is 2%) **Requires ManageMessages server permission.** | `.gc`
`.poll` | Creates a poll which requires users to send the number of the voting option to the bot. **Requires ManageMessages server permission.** | `.poll Question?;Answer1;Answ 2;A_3`
`.publicpoll` `.ppoll` | Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. **Requires ManageMessages server permission.** | `.ppoll Question?;Answer1;Answ 2;A_3`
`.poll` `.ppoll` | Creates a public poll which requires users to type a number of the voting option in the channel command is ran in. **Requires ManageMessages server permission.** | `.ppoll Question?;Answer1;Answ 2;A_3`
`.pollstats` | Shows the poll results without stopping the poll on this server. **Requires ManageMessages server permission.** | `.pollstats`
`.pollend` | Stops active poll on this server and prints the results in this channel. **Requires ManageMessages server permission.** | `.pollend`
`.typestart` | Starts a typing contest. | `.typestart`
@ -233,35 +233,36 @@ Commands and aliases | Description | Usage
### Music
Commands and aliases | Description | Usage
----------------|--------------|-------
`.play` `.start` | If no arguments are specified, acts as `.next 1` command. If you specify a song number, it will jump to that song. If you specify a search query, acts as a `.q` command | `.play` or `.play 5` or `.play Dream Of Venice`
`.queue` `.q` `.yq` | Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**. | `.q Dream Of Venice`
`.queuesearch` `.qs` `.yqs` | Search for top 5 youtube song result using keywords, and type the index of the song to play that song. Bot will join your voice channel. **You must be in a voice channel**. | `.qs Dream Of Venice`
`.listqueue` `.lq` | Lists 10 currently queued songs per page. Default page is 1. | `.lq` or `.lq 2`
`.next` `.n` | Goes to the next song in the queue. You have to be in the same voice channel as the bot. You can skip multiple songs, but in that case songs will not be requeued if .rcs or .rpl is enabled. | `.n` or `.n 5`
`.stop` `.s` | Stops the music and clears the playlist. Stays in the channel. | `.s`
`.destroy` `.d` | Completely stops the music and unbinds the bot from the channel. (may cause weird behaviour) | `.d`
`.pause` `.p` | Pauses or Unpauses the song. | `.p`
`.fairplay` `.fp` | Toggles fairplay. While enabled, the bot will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue. | `.fp`
`.queue` `.q` `.yq` | Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**. | `.q Dream Of Venice`
`.queuesearch` `.qs` `.yqs` | Search for top 5 youtube song result using keywords, and type the index of the song to play that song. Bot will join your voice channel. **You must be in a voice channel**. | `.qs Dream Of Venice`
`.soundcloudqueue` `.sq` | Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**. | `.sq Dream Of Venice`
`.listqueue` `.lq` | Lists 15 currently queued songs per page. Default page is 1. | `.lq` or `.lq 2`
`.nowplaying` `.np` | Shows the song that the bot is currently playing. | `.np`
`.volume` `.vol` | Sets the music playback volume (0-100%) | `.vol 50`
`.defvol` `.dv` | Sets the default music volume when music playback is started (0-100). Persists through restarts. | `.dv 80`
`.playlistshuffle` `.plsh` | Shuffles the current playlist. | `.plsh`
`.playlist` `.pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `.pl playlist link or name`
`.songremove` `.srm` | Remove a song by its # in the queue, or 'all' to remove all songs from the queue. | `.srm 5`
`.playlists` `.pls` | Lists all playlists. Paginated, 20 per page. Default page is 0. | `.pls 1`
`.deleteplaylist` `.delpls` | Deletes a saved playlist. Works only if you made it or if you are the bot owner. | `.delpls animu-5`
`.save` | Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes. | `.save classical1`
`.load` | Loads a saved playlist using its ID. Use `.pls` to list all saved playlists and `.save` to save new ones. | `.load 5`
`.fairplay` `.fp` | Toggles fairplay. While enabled, the bot will prioritize songs from users who didn't have their song recently played instead of the song's position in the queue. | `.fp`
`.soundcloudqueue` `.sq` | Queue a soundcloud song using keywords. Bot will join your voice channel. **You must be in a voice channel**. | `.sq Dream Of Venice`
`.soundcloudpl` `.scpl` | Queue a Soundcloud playlist using a link. | `.scpl soundcloudseturl`
`.localplaylst` `.lopl` | Queues all songs from a directory. **Bot owner only** | `.lopl C:/music/classical`
`.nowplaying` `.np` | Shows the song that the bot is currently playing. | `.np`
`.shuffle` `.sh` `.plsh` | Shuffles the current playlist. | `.plsh`
`.playlist` `.pl` | Queues up to 500 songs from a youtube playlist specified by a link, or keywords. | `.pl playlist link or name`
`.radio` `.ra` | Queues a radio stream from a link. It can be a direct mp3 radio stream, .m3u, .pls .asx or .xspf (Usage Video: <https://streamable.com/al54>) | `.ra radio link here`
`.local` `.lo` | Queues a local file by specifying a full path. **Bot owner only** | `.lo C:/music/mysong.mp3`
`.songremove` `.srm` | Remove a song by its # in the queue, or 'all' to remove all songs from the queue. | `.srm 5`
`.localplaylst` `.lopl` | Queues all songs from a directory. **Bot owner only** | `.lopl C:/music/classical`
`.move` `.mv` | Moves the bot to your voice channel. (works only if music is already playing) | `.mv`
`.movesong` `.ms` | Moves a song from one position to another. | `.ms 5>3`
`.setmaxqueue` `.smq` | Sets a maximum queue size. Supply 0 or no argument to have no limit. | `.smq 50` or `.smq`
`.setmaxplaytime` `.smp` | Sets a maximum number of seconds (>14) a song can run before being skipped automatically. Set 0 to have no limit. | `.smp 0` or `.smp 270`
`.reptcursong` `.rcs` | Toggles repeat of current song. | `.rcs`
`.rpeatplaylst` `.rpl` | Toggles repeat of all songs in the queue (every song that finishes is added to the end of the queue). | `.rpl`
`.save` | Saves a playlist under a certain name. Playlist name must be no longer than 20 characters and must not contain dashes. | `.save classical1`
`.load` | Loads a saved playlist using its ID. Use `.pls` to list all saved playlists and `.save` to save new ones. | `.load 5`
`.playlists` `.pls` | Lists all playlists. Paginated, 20 per page. Default page is 0. | `.pls 1`
`.deleteplaylist` `.delpls` | Deletes a saved playlist. Works 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`
`.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`
`.setmusicchannel` `.smch` | Sets the current channel as the default music output channel. This will output playing, finished, paused and removed songs to that channel instead of the channel where the first song was queued in. **Requires ManageMessages server permission.** | `.smch`
@ -411,7 +412,6 @@ Commands and aliases | Description | Usage
`.channeltopic` `.ct` | Sends current channel's topic as a message. | `.ct`
`.createinvite` `.crinv` | Creates a new invite which has infinite max uses and never expires. **Requires CreateInstantInvite channel permission.** | `.crinv`
`.shardstats` | Stats for shards. Paginated with 25 shards per page. | `.shardstats` or `.shardstats 2`
`.shardid` | Shows which shard is a certain guild on, by guildid. | `.shardid 117523346618318850`
`.stats` | Shows some basic stats for Nadeko. | `.stats`
`.showemojis` `.se` | Shows a name and a link to every SPECIAL emoji in the message. | `.se A message full of SPECIAL emojis`
`.listservers` | Lists servers the bot is on with some basic info. 15 per page. **Bot owner only** | `.listservers 3`
@ -421,10 +421,7 @@ Commands and aliases | Description | Usage
`.calcops` | Shows all available operations in the `.calc` command | `.calcops`
`.alias` `.cmdmap` | Create a custom alias for a certain Nadeko command. Provide no alias to remove the existing one. **Requires Administrator server permission.** | `.alias allin $bf 100 h` or `.alias "linux thingy" >loonix Spyware Windows`
`.aliaslist` `.cmdmaplist` `.aliases` | Shows the list of currently set aliases. Paginated. | `.aliaslist` or `.aliaslist 3`
`.scsc` | Starts an instance of cross server channel. You will get a token as a DM that other people will use to tune in to the same instance. **Bot owner only** | `.scsc`
`.jcsc` | Joins current channel to an instance of cross server channel using the token. **Requires ManageServer server permission.** | `.jcsc TokenHere`
`.lcsc` | Leaves a cross server channel instance from this channel. **Requires ManageServer server permission.** | `.lcsc`
`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no channel is supplied, it defaults to current one. | `.sinfo Some Server`
`.serverinfo` `.sinfo` | Shows info about the server the bot is on. If no server is supplied, it defaults to current one. | `.sinfo Some Server`
`.channelinfo` `.cinfo` | Shows info about the channel. If no channel is supplied, it defaults to current one. | `.cinfo #some-channel`
`.userinfo` `.uinfo` | Shows info about the user. If no user is supplied, it defaults a user running the command. | `.uinfo @SomeUser`
`.activity` | Checks for spammers. **Bot owner only** | `.activity`

View File

@ -36,14 +36,4 @@ For example:
Now if you try to trigger `/o/`, it won't print anything.
###Placeholders!
There are currently three different placeholders which we will look at, with more placeholders potentially coming in the future.
| Placeholder | Description | Example Usage | Usage |
|:-----------:|-------------|---------------|-------|
|`%mention%`|The `%mention%` placeholder is triggered when you type `@BotName` - It's important to note that if you've given the bot a custom nickname, this trigger won't work!|```.acr "Hello %mention%" I, %mention%, also say hello!```|Input: "Hello @BotName" Output: "I, @BotName, also say hello!"|
|`%user%`|The `%user%` placeholder mentions the person who said the command|`.acr "Who am I?" You are %user%!`|Input: "Who am I?" Output: "You are @Username!"|
|`%rng%`|The `%rng%` placeholder generates a random number between 0 and 10. You can also specify a custom range (%rng1-100%) even with negative numbers: `%rng-9--1%` (from -9 to -1) . |`.acr "Random number" %rng%`|Input: "Random number" Output: "2"|
|`%rnduser%`|The `%rnduser%` placeholder mentions a random user from the server. |`.acr "Random user" %rnduser%`|Input: "Random number" Output: @SomeUser|
|`%target%`|The `%target%` placeholder is used to make Nadeko Mention another person or phrase, it is only supported as part of the response|`.acr "Say this: " %target%`|Input: "Say this: I, @BotName, am a parrot!". Output: "I, @BotName, am a parrot!".|
Thanks to Nekai for being creative. <3
To learn about placeholders, go [here](Placeholders.md)

View File

@ -17,8 +17,12 @@ If you do not see `credentials.json` you will need to rename `credentials_exampl
"MashapeKey": "4UrKpcWXc2mshS8RKi00000y8Kf5p1Q8kI6jsn32bmd8oVWiY7",
"OsuApiKey": "4c8c8fdff8e1234581725db27fd140a7d93320d6",
"PatreonAccessToken": "",
"PatreonCampaignId": "334038",
"Db": null,
"TotalShards": 1
"TotalShards": 1,
"ShardRunCommand": "",
"ShardRunArguments": "",
"ShardRunPort": null
}
```
-----
@ -152,10 +156,11 @@ It should look like:
- You can get this key [here.](https://osu.ppy.sh/p/api)
- **PatreonAccessToken**
- For Patreon creators only.
- **PatreonCampaignId**
- For Patreon creators only. Id of your campaign.
- **TotalShards**
- Required if the bot will be connected to more than 1500 servers.
- Most likely unnecessary to change until your bot is added to more than 1500 servers.
-----
## DB files
@ -163,6 +168,7 @@ It should look like:
Nadeko saves all the settings and infomations in `NadekoBot.db` file here:
`NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data/NadekoBot.db` (macOS and Linux)
`NadekoBot\system\data` (Windows)
in order to open the database file you will need [DB Browser for SQLite](http://sqlitebrowser.org/).
*NOTE: You don't have to worry if you don't have `NadekoBot.db` file, it gets auto created once you run the bot successfully.*
@ -181,6 +187,23 @@ in order to open the database file you will need [DB Browser for SQLite](http://
and that will save all the changes.
## Sharding your bot
- **ShardRunCommand**
- Command with which to run shards 1+
- Required if you're sharding your bot on windows using .exe, or in a custom way.
- This internally defaults to `dotnet`
- For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to something like this: `C:\Program Files\NadekoBot\system\NadekoBot.exe`
- **ShardRunArguments**
- Arguments to the shard run command
- Required if you're sharding your bot on windows using .exe, or in a custom way.
- This internally defaults to `run -c Release -- {0} {1} {2}` which will be enough to run linux and other 'from source' setups
- {0} will be replaced by the `shard ID` of the shard being ran, {1} by the shard 0's process id, and {2} by the port shard communication is happening on
- If shard0 (main window) is closed, all other shards will close too
- For example, if you want to shard your NadekoBot which you installed using windows installer, you would want to set it to `{0} {1} {2}`
- **ShardRunPort**
- Bot uses a random UDP port in [5000, 6000) range for communication between shards
![nadekodb](https://cdn.discordapp.com/attachments/251504306010849280/254067055240806400/nadekodb.gif)

25
docs/Placeholders.md Normal file
View File

@ -0,0 +1,25 @@
Placeholders are used in Quotes, Custom Reactions, Greet/bye messages, playing statuses, and a few other places.
They can be used to make the message more user friendly, generate random numbers or pictures, etc...
Some features have their own specific placeholders which are noted in that feature's command help. Some placeholders are not available in certain features because they don't make sense there.
### Here is a list of the usual placeholders:
- `%mention%` - Mention the bot
- `%shardid%` - Shard id
- `%server%` - Server name
- `%sid%` - Server Id
- `%channel%` - Channel mention
- `%chname%` - Channel mention
- `%cid%` - Channel Id
- `%user%` - User mention
- `%id%` or `%uid%` - User Id
- `%userfull%` - Username#discriminator
- `%userdiscrim%` - discriminator (for example 1234)
- `%rngX-Y%` - Replace X and Y with the range (for example `%rng5-10%` - random between 5 and 10)
- `%time%` - Bot time
- `%server_time%` - Time on this server, set with `.timezone` command
**If you're using placeholders in embeds, don't use %user% and in titles, footers and field names. They will not show properly.**
![img](http://i.imgur.com/lNNNfs1.png)

View File

@ -8,7 +8,7 @@ Follow the respective guide for your operating system found here [Docker Engine
For this guide we will be using the folder /nadeko as our config root folder.
```bash
docker create --name=nadeko -v /nadeko/:/root/nadeko uirel/nadeko:1.4
docker create --name=nadeko -v /nadeko/conf/:/root/nadeko -v /nadeko/data:/opt/NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data uirel/nadeko:1.4
```
-If you are coming from a previous version of nadeko (the old docker) make sure your crednetials.json has been copied into this directory and is the only thing in this folder.
@ -37,7 +37,9 @@ The following commands are required for the default options
`docker stop nadeko; docker rm nadeko`
`docker create --name=nadeko -v /nadeko/:/root/nadeko uirel/nadeko:1.4`
```
docker create --name=nadeko -v /nadeko/conf/:/root/nadeko -v /nadeko/data:/opt/NadekoBot/src/NadekoBot/bin/Release/netcoreapp1.1/data uirel/nadeko:1.4
```
`docker start nadeko`

View File

@ -3,7 +3,6 @@
#### Prerequisites
- [Notepad++][Notepad++] (or some other decent text editor)
- Windows 8 or later
- [.NET Core SDK (Command line / other)][.NET Core SDK]
- [Create Discord Bot application](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/#creating-discord-bot-application) and [Invite the bot to your server](http://nadekobot.readthedocs.io/en/latest/JSON%20Explanations/#inviting-your-bot-to-your-server).
#### Guide

View File

@ -28,6 +28,7 @@ If you want to contribute, be sure to PR on the **[1.4][1.4]** branch.
- [Permissions System](Permissions System.md)
- [JSON Explanations](JSON Explanations.md)
- [Custom Reactions](Custom Reactions.md)
- [Placeholders](Placeholders.md)
- [Frequently Asked Questions](Frequently Asked Questions.md)
- [Contribution Guide](Contribution Guide.md)
- [Donate](Donate.md)

View File

@ -14,9 +14,10 @@ pages:
- Readme: Readme.md
- Commands List: Commands List.md
- Features Explained:
- Permissions System: Permissions System.md
- JSON Explanations: JSON Explanations.md
- Permissions System: Permissions System.md
- Custom Reactions: Custom Reactions.md
- Placeholders: Placeholders.md
- Frequently Asked Questions: Frequently Asked Questions.md
- Contribution Guide: Contribution Guide.md
- ❤ Donate ❤: Donate.md

View File

@ -1,6 +1,8 @@
using Discord;
using NadekoBot.Extensions;
using Newtonsoft.Json;
using NLog;
using System;
namespace NadekoBot.DataStructures
{
@ -31,18 +33,30 @@ namespace NadekoBot.DataStructures
public EmbedBuilder ToEmbed()
{
var embed = new EmbedBuilder()
.WithTitle(Title)
.WithDescription(Description)
.WithColor(new Discord.Color(Color));
var embed = new EmbedBuilder();
if (!string.IsNullOrWhiteSpace(Title))
embed.WithTitle(Title);
if (!string.IsNullOrWhiteSpace(Description))
embed.WithDescription(Description);
embed.WithColor(new Discord.Color(Color));
if (Footer != null)
embed.WithFooter(efb => efb.WithIconUrl(Footer.IconUrl).WithText(Footer.Text));
embed.WithThumbnailUrl(Thumbnail)
.WithImageUrl(Image);
embed.WithFooter(efb =>
{
efb.WithText(Footer.Text);
if (Uri.IsWellFormedUriString(Footer.IconUrl, UriKind.Absolute))
efb.WithIconUrl(Footer.IconUrl);
});
if (Thumbnail != null && Uri.IsWellFormedUriString(Thumbnail, UriKind.Absolute))
embed.WithThumbnailUrl(Thumbnail);
if(Image != null && Uri.IsWellFormedUriString(Image, UriKind.Absolute))
embed.WithImageUrl(Image);
if (Fields != null)
foreach (var f in Fields)
{
if(!string.IsNullOrWhiteSpace(f.Name) && !string.IsNullOrWhiteSpace(f.Value))
embed.AddField(efb => efb.WithName(f.Name).WithValue(f.Value).WithIsInline(f.Inline));
}
@ -59,6 +73,12 @@ namespace NadekoBot.DataStructures
{
var crembed = JsonConvert.DeserializeObject<CREmbed>(input);
if(crembed.Fields != null && crembed.Fields.Length > 0)
foreach (var f in crembed.Fields)
{
f.Name = f.Name.TrimTo(256);
f.Value = f.Value.TrimTo(1024);
}
if (!crembed.IsValid)
return false;

View File

@ -13,6 +13,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors
/// Try to execute some logic within some module's service.
/// </summary>
/// <returns>Whether it should block other command executions after it.</returns>
Task<bool> TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg);
Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg);
}
}

View File

@ -6,7 +6,7 @@ namespace NadekoBot.DataStructures.ModuleBehaviors
{
public interface ILateBlocker
{
Task<bool> TryBlockLate(DiscordShardedClient client, IUserMessage msg, IGuild guild,
Task<bool> TryBlockLate(DiscordSocketClient client, IUserMessage msg, IGuild guild,
IMessageChannel channel, IUser user, string moduleName, string commandName);
}
}

View File

@ -9,6 +9,6 @@ namespace NadekoBot.DataStructures.ModuleBehaviors
/// </summary>
public interface ILateExecutor
{
Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg);
Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg);
}
}

View File

@ -0,0 +1,116 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
public class PoopyRingBuffer : IDisposable
{
// readpos == writepos means empty
// writepos == readpos - 1 means full
private byte[] buffer;
public int Capacity { get; }
private int _readPos = 0;
private int ReadPos
{
get => _readPos;
set => _readPos = value;
}
private int _writePos = 0;
private int WritePos
{
get => _writePos;
set => _writePos = value;
}
public int Length => ReadPos <= WritePos
? WritePos - ReadPos
: Capacity - (ReadPos - WritePos);
public int RemainingCapacity
{
get => Capacity - Length - 1;
}
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
public PoopyRingBuffer(int capacity = 81920 * 100)
{
this.Capacity = capacity + 1;
this.buffer = new byte[this.Capacity];
}
public int Read(byte[] b, int offset, int toRead)
{
if (WritePos == ReadPos)
return 0;
if (toRead > Length)
toRead = Length;
if (WritePos > ReadPos)
{
Array.Copy(buffer, ReadPos, b, offset, toRead);
ReadPos += toRead;
}
else
{
var toEnd = Capacity - ReadPos;
var firstRead = toRead > toEnd ?
toEnd :
toRead;
Array.Copy(buffer, ReadPos, b, offset, firstRead);
ReadPos += firstRead;
var secondRead = toRead - firstRead;
if (secondRead > 0)
{
Array.Copy(buffer, 0, b, offset + firstRead, secondRead);
ReadPos = secondRead;
}
}
return toRead;
}
public bool Write(byte[] b, int offset, int toWrite)
{
while (toWrite > RemainingCapacity)
return false;
if (toWrite == 0)
return true;
if (WritePos < ReadPos)
{
Array.Copy(b, offset, buffer, WritePos, toWrite);
WritePos += toWrite;
}
else
{
var toEnd = Capacity - WritePos;
var firstWrite = toWrite > toEnd ?
toEnd :
toWrite;
Array.Copy(b, offset, buffer, WritePos, firstWrite);
var secondWrite = toWrite - firstWrite;
if (secondWrite > 0)
{
Array.Copy(b, offset + firstWrite, buffer, 0, secondWrite);
WritePos = secondWrite;
}
else
{
WritePos += firstWrite;
if (WritePos == Capacity)
WritePos = 0;
}
}
return true;
}
public void Dispose()
{
buffer = null;
}
}
}

View File

@ -0,0 +1,148 @@
using Discord;
using Discord.Commands;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NadekoBot.Services.Music;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Text.RegularExpressions;
namespace NadekoBot.DataStructures.Replacements
{
public class ReplacementBuilder
{
private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
private ConcurrentDictionary<string, Func<string>> _reps = new ConcurrentDictionary<string, Func<string>>();
private ConcurrentDictionary<Regex, Func<Match, string>> _regex = new ConcurrentDictionary<Regex, Func<Match, string>>();
public ReplacementBuilder()
{
WithRngRegex();
}
public ReplacementBuilder WithDefault(IUser usr, IMessageChannel ch, IGuild g, DiscordSocketClient client)
{
return this.WithUser(usr)
.WithChannel(ch)
.WithServer(client, g)
.WithClient(client);
}
public ReplacementBuilder WithDefault(ICommandContext ctx) =>
WithDefault(ctx.User, ctx.Channel, ctx.Guild, (DiscordSocketClient)ctx.Client);
public ReplacementBuilder WithClient(DiscordSocketClient client)
{
_reps.TryAdd("%mention%", () => $"<@{client.CurrentUser.Id}>");
_reps.TryAdd("%shardid%", () => client.ShardId.ToString());
_reps.TryAdd("%time%", () => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()));
return this;
}
public ReplacementBuilder WithServer(DiscordSocketClient client, IGuild g)
{
_reps.TryAdd("%sid%", () => g == null ? "DM" : g.Id.ToString());
_reps.TryAdd("%server%", () => g == null ? "DM" : g.Name);
_reps.TryAdd("%server_time%", () =>
{
TimeZoneInfo to = TimeZoneInfo.Local;
if (g != null)
{
if (GuildTimezoneService.AllServices.TryGetValue(client.CurrentUser.Id, out var tz))
to = tz.GetTimeZoneOrDefault(g.Id) ?? TimeZoneInfo.Local;
}
return TimeZoneInfo.ConvertTime(DateTime.UtcNow,
TimeZoneInfo.Utc,
to).ToString("HH:mm ") + to.StandardName.GetInitials();
});
return this;
}
public ReplacementBuilder WithChannel(IMessageChannel ch)
{
_reps.TryAdd("%channel%", () => (ch as ITextChannel)?.Mention ?? "#" + ch.Name);
_reps.TryAdd("%chname%", () => ch.Name);
_reps.TryAdd("%cid%", () => ch?.Id.ToString());
return this;
}
public ReplacementBuilder WithUser(IUser user)
{
_reps.TryAdd("%user%", () => user.Mention);
_reps.TryAdd("%userfull%", () => user.ToString());
_reps.TryAdd("%username%", () => user.Username);
_reps.TryAdd("%userdiscrim%", () => user.Discriminator);
_reps.TryAdd("%id%", () => user.Id.ToString());
_reps.TryAdd("%uid%", () => user.Id.ToString());
return this;
}
public ReplacementBuilder WithStats(DiscordSocketClient c)
{
_reps.TryAdd("%servers%", () => c.Guilds.Count.ToString());
_reps.TryAdd("%users%", () => c.Guilds.Sum(s => s.Users.Count).ToString());
return this;
}
public ReplacementBuilder WithMusic(MusicService ms)
{
_reps.TryAdd("%playing%", () =>
{
var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.Current.Current != null);
if (cnt != 1) return cnt.ToString();
try
{
var mp = ms.MusicPlayers.FirstOrDefault();
var title = mp.Value?.Current.Current?.Title;
return title ?? "No songs";
}
catch
{
return "error";
}
});
_reps.TryAdd("%queued%", () => ms.MusicPlayers.Sum(kvp => kvp.Value.QueueArray().Songs.Length).ToString());
return this;
}
public ReplacementBuilder WithRngRegex()
{
var rng = new NadekoRandom();
_regex.TryAdd(rngRegex, (match) =>
{
int from = 0;
int.TryParse(match.Groups["from"].ToString(), out from);
int to = 0;
int.TryParse(match.Groups["to"].ToString(), out to);
if (from == 0 && to == 0)
{
return rng.Next(0, 11).ToString();
}
if (from >= to)
return string.Empty;
return rng.Next(from, to + 1).ToString();
});
return this;
}
public ReplacementBuilder WithOverride(string key, Func<string> output)
{
_reps.AddOrUpdate(key, output, delegate { return output; });
return this;
}
public Replacer Build()
{
return new Replacer(_reps.Select(x => (x.Key, x.Value)).ToArray(), _regex.Select(x => (x.Key, x.Value)).ToArray());
}
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace NadekoBot.DataStructures.Replacements
{
public class Replacer
{
private readonly IEnumerable<(string Key, Func<string> Text)> _replacements;
private readonly IEnumerable<(Regex Regex, Func<Match, string> Replacement)> _regex;
public Replacer(IEnumerable<(string, Func<string>)> replacements, IEnumerable<(Regex, Func<Match, string>)> regex)
{
_replacements = replacements;
_regex = regex;
}
public string Replace(string input)
{
if (string.IsNullOrWhiteSpace(input))
return input;
foreach (var item in _replacements)
{
if (input.Contains(item.Key))
input = input.Replace(item.Key, item.Text());
}
foreach (var item in _regex)
{
input = item.Regex.Replace(input, (m) => item.Replacement(m));
}
return input;
}
public void Replace(CREmbed embedData)
{
embedData.PlainText = Replace(embedData.PlainText);
embedData.Description = Replace(embedData.Description);
embedData.Title = Replace(embedData.Title);
if (embedData.Fields != null)
foreach (var f in embedData.Fields)
{
f.Name = Replace(f.Name);
f.Value = Replace(f.Value);
}
if (embedData.Footer != null)
embedData.Footer.Text = Replace(embedData.Footer.Text);
}
}
}

View File

@ -0,0 +1,216 @@
using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
namespace NadekoBot.DataStructures
{
public class SearchImageCacher
{
private readonly NadekoRandom _rng;
private readonly ConcurrentDictionary<DapiSearchType, SemaphoreSlim> _locks = new ConcurrentDictionary<DapiSearchType, SemaphoreSlim>();
private readonly SortedSet<ImageCacherObject> _cache;
private readonly Logger _log;
public SearchImageCacher()
{
_log = LogManager.GetCurrentClassLogger();
_rng = new NadekoRandom();
_cache = new SortedSet<ImageCacherObject>();
}
public async Task<ImageCacherObject> GetImage(string tag, bool forceExplicit, DapiSearchType type)
{
tag = tag?.ToLowerInvariant();
if (type == DapiSearchType.E621)
tag = tag?.Replace("yuri", "female/female");
var _lock = GetLock(type);
await _lock.WaitAsync();
try
{
ImageCacherObject[] imgs;
if (!string.IsNullOrWhiteSpace(tag))
{
imgs = _cache.Where(x => x.Tags.IsSupersetOf(tag.Split('+')) && x.SearchType == type && (!forceExplicit || x.Rating == "e")).ToArray();
}
else
{
tag = null;
imgs = _cache.Where(x => x.SearchType == type).ToArray();
}
ImageCacherObject img;
if (imgs.Length == 0)
img = null;
else
img = imgs[_rng.Next(imgs.Length)];
if (img != null)
{
_cache.Remove(img);
return img;
}
else
{
var images = await DownloadImages(tag, forceExplicit, type).ConfigureAwait(false);
if (images.Length == 0)
return null;
var toReturn = images[_rng.Next(images.Length)];
foreach (var dledImg in images)
{
if(dledImg != toReturn)
_cache.Add(dledImg);
}
return toReturn;
}
}
finally
{
_lock.Release();
}
}
private SemaphoreSlim GetLock(DapiSearchType type)
{
return _locks.GetOrAdd(type, _ => new SemaphoreSlim(1, 1));
}
public async Task<ImageCacherObject[]> DownloadImages(string tag, bool isExplicit, DapiSearchType type)
{
_log.Info($"Loading extra images from {type}");
tag = tag?.Replace(" ", "_").ToLowerInvariant();
if (isExplicit)
tag = "rating%3Aexplicit+" + tag;
var website = "";
switch (type)
{
case DapiSearchType.Safebooru:
website = $"https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=1000&tags={tag}";
break;
case DapiSearchType.E621:
website = $"https://e621.net/post/index.json?limit=1000&tags={tag}";
break;
case DapiSearchType.Danbooru:
website = $"http://danbooru.donmai.us/posts.json?limit=200&tags={tag}";
break;
case DapiSearchType.Gelbooru:
website = $"http://gelbooru.com/index.php?page=dapi&s=post&q=index&limit=1000&tags={tag}";
break;
case DapiSearchType.Rule34:
website = $"https://rule34.xxx/index.php?page=dapi&s=post&q=index&limit=100&tags={tag}";
break;
case DapiSearchType.Konachan:
website = $"https://konachan.com/post.json?s=post&q=index&limit=1000&tags={tag}";
break;
case DapiSearchType.Yandere:
website = $"https://yande.re/post.json?limit=1000&tags={tag}";
break;
}
using (var http = new HttpClient())
{
http.AddFakeHeaders();
if (type == DapiSearchType.Konachan || type == DapiSearchType.Yandere ||
type == DapiSearchType.E621 || type == DapiSearchType.Danbooru)
{
var data = await http.GetStringAsync(website).ConfigureAwait(false);
return JsonConvert.DeserializeObject<DapiImageObject[]>(data)
.Where(x => x.File_Url != null)
.Select(x => new ImageCacherObject(x, type))
.ToArray();
}
return (await LoadXmlAsync(website, type)).ToArray();
}
}
private async Task<ImageCacherObject[]> LoadXmlAsync(string website, DapiSearchType type)
{
var list = new List<ImageCacherObject>(1000);
using (var http = new HttpClient())
{
using (var reader = XmlReader.Create(await http.GetStreamAsync(website), new XmlReaderSettings()
{
Async = true,
}))
{
while (await reader.ReadAsync())
{
if (reader.NodeType == XmlNodeType.Element &&
reader.Name == "post")
{
list.Add(new ImageCacherObject(new DapiImageObject()
{
File_Url = reader["file_url"],
Tags = reader["tags"],
Rating = reader["rating"] ?? "e"
}, type));
}
}
}
}
return list.ToArray();
}
}
public class ImageCacherObject : IComparable<ImageCacherObject>
{
public DapiSearchType SearchType { get; }
public string FileUrl { get; }
public HashSet<string> Tags { get; }
public string Rating { get; }
public ImageCacherObject(DapiImageObject obj, DapiSearchType type)
{
if (type == DapiSearchType.Danbooru)
this.FileUrl = "https://danbooru.donmai.us" + obj.File_Url;
else
this.FileUrl = obj.File_Url.StartsWith("http") ? obj.File_Url : "https:" + obj.File_Url;
this.SearchType = type;
this.Rating = obj.Rating;
this.Tags = new HashSet<string>((obj.Tags ?? obj.Tag_String).Split(' '));
}
public override string ToString()
{
return FileUrl;
}
public int CompareTo(ImageCacherObject other)
{
return FileUrl.CompareTo(other.FileUrl);
}
}
public class DapiImageObject
{
public string File_Url { get; set; }
public string Tags { get; set; }
public string Tag_String { get; set; }
public string Rating { get; set; }
}
public enum DapiSearchType
{
Safebooru,
E621,
Gelbooru,
Konachan,
Rule34,
Yandere,
Danbooru
}
}

View File

@ -0,0 +1,22 @@
using Discord.Commands;
using Discord.WebSocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
public class Shard0Precondition : PreconditionAttribute
{
public override Task<PreconditionResult> CheckPermissions(ICommandContext context, CommandInfo command, IServiceProvider services)
{
var c = (DiscordSocketClient)context.Client;
if (c.ShardId == 0)
return Task.FromResult(PreconditionResult.FromSuccess());
else
return Task.FromResult(PreconditionResult.FromError("Must be ran from shard #0"));
}
}
}

View File

@ -0,0 +1,17 @@
using Discord;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures.ShardCom
{
public class ShardComMessage
{
public int ShardId { get; set; }
public ConnectionState ConnectionState { get; set; }
public int Guilds { get; set; }
public DateTime Time { get; set; }
}
}

View File

@ -0,0 +1,29 @@
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures.ShardCom
{
public class ShardComClient
{
private int port;
public ShardComClient(int port)
{
this.port = port;
}
public async Task Send(ShardComMessage data)
{
var msg = JsonConvert.SerializeObject(data);
using (var client = new UdpClient())
{
var bytes = Encoding.UTF8.GetBytes(msg);
await client.SendAsync(bytes, bytes.Length, IPAddress.Loopback.ToString(), port).ConfigureAwait(false);
}
}
}
}

View File

@ -0,0 +1,40 @@
using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures.ShardCom
{
public class ShardComServer : IDisposable
{
private readonly UdpClient _client;
public ShardComServer(int port)
{
_client = new UdpClient(port);
}
public void Start()
{
Task.Run(async () =>
{
var ip = new IPEndPoint(IPAddress.Any, 0);
while (true)
{
var recv = await _client.ReceiveAsync();
var data = Encoding.UTF8.GetString(recv.Buffer);
var _ = OnDataReceived(JsonConvert.DeserializeObject<ShardComMessage>(data));
}
});
}
public void Dispose()
{
_client.Dispose();
}
public event Func<ShardComMessage, Task> OnDataReceived = delegate { return Task.CompletedTask; };
}
}

View File

@ -0,0 +1,23 @@
using Discord.Commands;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace NadekoBot.DataStructures
{
//public class SyncPrecondition : PreconditionAttribute
//{
// public override Task<PreconditionResult> CheckPermissions(ICommandContext context,
// CommandInfo command,
// IServiceProvider services)
// {
// }
//}
//public enum SyncType
//{
// Guild
//}
}

View File

@ -1,6 +1,7 @@
using Discord.Commands;
using NadekoBot.Services;
using NadekoBot.Services.CustomReactions;
using System;
using System.Linq;
using System.Threading.Tasks;
@ -17,7 +18,7 @@ namespace NadekoBot.TypeReaders
_cmdHandler = cmdHandler;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{
input = input.ToUpperInvariant();
var prefix = _cmdHandler.GetPrefix(context.Guild);
@ -44,7 +45,7 @@ namespace NadekoBot.TypeReaders
_crs = crs;
}
public override async Task<TypeReaderResult> Read(ICommandContext context, string input)
public override async Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{
input = input.ToUpperInvariant();
@ -64,7 +65,7 @@ namespace NadekoBot.TypeReaders
}
}
var cmd = await base.Read(context, input);
var cmd = await base.Read(context, input, _);
if (cmd.IsSuccess)
{
return TypeReaderResult.FromSuccess(new CommandOrCrInfo(((CommandInfo)cmd.Values.First().Value).Name));

View File

@ -14,7 +14,7 @@ namespace NadekoBot.TypeReaders
_gts = gts;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{
if (!DateTime.TryParse(input, out var dt))
return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Input string is in an incorrect format."));

View File

@ -1,5 +1,6 @@
using Discord.Commands;
using Discord.WebSocket;
using System;
using System.Linq;
using System.Threading.Tasks;
@ -7,13 +8,13 @@ namespace NadekoBot.TypeReaders
{
public class GuildTypeReader : TypeReader
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public GuildTypeReader(DiscordShardedClient client)
public GuildTypeReader(DiscordSocketClient client)
{
_client = client;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{
input = input.Trim().ToLowerInvariant();
var guilds = _client.Guilds;

View File

@ -1,5 +1,6 @@
using Discord.Commands;
using NadekoBot.Extensions;
using System;
using System.Linq;
using System.Threading.Tasks;
@ -14,7 +15,7 @@ namespace NadekoBot.TypeReaders
_cmds = cmds;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{
input = input.ToUpperInvariant();
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToUpperInvariant() == input)?.Key;
@ -34,7 +35,7 @@ namespace NadekoBot.TypeReaders
_cmds = cmds;
}
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{
input = input.ToLowerInvariant();
var module = _cmds.Modules.GroupBy(m => m.GetTopLevelModule()).FirstOrDefault(m => m.Key.Name.ToLowerInvariant() == input)?.Key;

View File

@ -1,6 +1,7 @@
using Discord.Commands;
using System.Threading.Tasks;
using NadekoBot.Modules.Permissions;
using System;
namespace NadekoBot.TypeReaders
{
@ -9,7 +10,7 @@ namespace NadekoBot.TypeReaders
/// </summary>
public class PermissionActionTypeReader : TypeReader
{
public override Task<TypeReaderResult> Read(ICommandContext context, string input)
public override Task<TypeReaderResult> Read(ICommandContext context, string input, IServiceProvider _)
{
input = input.ToUpperInvariant();
switch (input)

View File

@ -126,16 +126,15 @@ namespace NadekoBot.Modules.Administration
{
var guser = (IGuildUser)Context.User;
var userRoles = user.GetRoles();
if (guser.Id != Context.Guild.OwnerId &&
(user.Id == Context.Guild.OwnerId || guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position)))
var userRoles = user.GetRoles().Except(new[] { guser.Guild.EveryoneRole });
if (user.Id == Context.Guild.OwnerId || (Context.User.Id != Context.Guild.OwnerId && guser.GetRoles().Max(x => x.Position) <= userRoles.Max(x => x.Position)))
return;
try
{
await user.RemoveRolesAsync(userRoles).ConfigureAwait(false);
await ReplyConfirmLocalized("rar", Format.Bold(user.ToString())).ConfigureAwait(false);
}
catch
catch (Exception)
{
await ReplyErrorLocalized("rar_err").ConfigureAwait(false);
}

View File

@ -27,17 +27,15 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task RotatePlaying()
{
lock (_locker)
{
bool enabled;
using (var uow = _db.UnitOfWork)
{
var config = uow.BotConfig.GetOrCreate();
_service.RotatingStatuses = config.RotatingStatuses = !config.RotatingStatuses;
enabled = config.RotatingStatuses = !config.RotatingStatuses;
uow.Complete();
}
}
if (_service.RotatingStatuses)
if (enabled)
await ReplyConfirmLocalized("ropl_enabled").ConfigureAwait(false);
else
await ReplyConfirmLocalized("ropl_disabled").ConfigureAwait(false);
@ -52,7 +50,6 @@ namespace NadekoBot.Modules.Administration
var config = uow.BotConfig.GetOrCreate();
var toAdd = new PlayingStatus { Status = status };
config.RotatingStatusMessages.Add(toAdd);
_service.RotatingStatusMessages.Add(toAdd);
await uow.CompleteAsync();
}
@ -63,13 +60,13 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task ListPlaying()
{
if (!_service.RotatingStatusMessages.Any())
if (!_service.BotConfig.RotatingStatusMessages.Any())
await ReplyErrorLocalized("ropl_not_set").ConfigureAwait(false);
else
{
var i = 1;
await ReplyConfirmLocalized("ropl_list",
string.Join("\n\t", _service.RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}")))
string.Join("\n\t", _service.BotConfig.RotatingStatusMessages.Select(rs => $"`{i++}.` {rs.Status}")))
.ConfigureAwait(false);
}
@ -90,7 +87,6 @@ namespace NadekoBot.Modules.Administration
return;
msg = config.RotatingStatusMessages[index].Status;
config.RotatingStatusMessages.RemoveAt(index);
_service.RotatingStatusMessages.RemoveAt(index);
await uow.CompleteAsync();
}
await ReplyConfirmLocalized("reprm", msg).ConfigureAwait(false);

View File

@ -140,7 +140,7 @@ namespace NadekoBot.Modules.Administration
await uow.CompleteAsync();
}
await Context.Channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, page, (curPage) =>
await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, (curPage) =>
{
return new EmbedBuilder()
.WithTitle(GetText("self_assign_list", roleCnt))

View File

@ -13,6 +13,9 @@ using NadekoBot.Services;
using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
using NadekoBot.Services.Administration;
using System.Diagnostics;
using NadekoBot.DataStructures;
using NadekoBot.Services.Music;
namespace NadekoBot.Modules.Administration
{
@ -25,16 +28,18 @@ namespace NadekoBot.Modules.Administration
private static readonly object _locker = new object();
private readonly SelfService _service;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly IImagesService _images;
private readonly MusicService _music;
public SelfCommands(DbService db, SelfService service, DiscordShardedClient client,
IImagesService images)
public SelfCommands(DbService db, SelfService service, DiscordSocketClient client,
MusicService music, IImagesService images)
{
_db = db;
_service = service;
_client = client;
_images = images;
_music = music;
}
[NadekoCommand, Usage, Description, Aliases]
@ -204,28 +209,28 @@ namespace NadekoBot.Modules.Administration
}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ConnectShard(int shardid)
{
var shard = _client.GetShard(shardid);
if (shard == null)
{
await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false);
return;
}
try
{
await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false);
await shard.StartAsync().ConfigureAwait(false);
await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn(ex);
}
}
//todo 2 shard commands
//[NadekoCommand, Usage, Description, Aliases]
//[Shard0Precondition]
//[OwnerOnly]
//public async Task RestartShard(int shardid)
//{
// if (shardid == 0 || shardid > b)
// {
// await ReplyErrorLocalized("no_shard_id").ConfigureAwait(false);
// return;
// }
// try
// {
// await ReplyConfirmLocalized("shard_reconnecting", Format.Bold("#" + shardid)).ConfigureAwait(false);
// await shard.StartAsync().ConfigureAwait(false);
// await ReplyConfirmLocalized("shard_reconnected", Format.Bold("#" + shardid)).ConfigureAwait(false);
// }
// catch (Exception ex)
// {
// _log.Warn(ex);
// }
//}
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
@ -266,6 +271,7 @@ namespace NadekoBot.Modules.Administration
// ignored
}
await Task.Delay(2000).ConfigureAwait(false);
try { await _music.DestroyAllPlayers().ConfigureAwait(false); } catch { }
Environment.Exit(0);
}
@ -417,8 +423,10 @@ namespace NadekoBot.Modules.Administration
[OwnerOnly]
public async Task ReloadImages()
{
var time = _images.Reload();
await ReplyConfirmLocalized("images_loaded", time.TotalSeconds.ToString("F3")).ConfigureAwait(false);
var sw = Stopwatch.StartNew();
_images.Reload();
sw.Stop();
await ReplyConfirmLocalized("images_loaded", sw.Elapsed.TotalSeconds.ToString("F3")).ConfigureAwait(false);
}
private static UserStatus SettableUserStatusToUserStatus(SettableUserStatus sus)

View File

@ -36,7 +36,7 @@ namespace NadekoBot.Modules.Administration
.ToArray();
var timezonesPerPage = 20;
await Context.Channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client, page,
await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page,
(curPage) => new EmbedBuilder()
.WithOkColor()
.WithTitle(GetText("timezones_available"))

View File

@ -28,6 +28,7 @@ namespace NadekoBot.Modules.Administration
_muteService = muteService;
}
//todo move to service
private async Task<PunishmentAction?> InternalWarn(IGuild guild, ulong userId, string modName, string reason)
{
if (string.IsNullOrWhiteSpace(reason))
@ -109,7 +110,7 @@ namespace NadekoBot.Modules.Administration
{
try
{
await (await user.CreateDMChannelAsync()).EmbedAsync(new EmbedBuilder().WithErrorColor()
await (await user.GetOrCreateDMChannelAsync()).EmbedAsync(new EmbedBuilder().WithErrorColor()
.WithDescription(GetText("warned_on", Context.Guild.ToString()))
.AddField(efb => efb.WithName(GetText("moderator")).WithValue(Context.User.ToString()))
.AddField(efb => efb.WithName(GetText("reason")).WithValue(reason ?? "-")))
@ -131,24 +132,27 @@ namespace NadekoBot.Modules.Administration
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
[Priority(1)]
public Task Warnlog(int page, IGuildUser user)
=> Warnlog(page, user.Id);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
[Priority(0)]
public Task Warnlog(IGuildUser user)
=> Warnlog(user.Id);
=> Context.User.Id == user.Id || ((IGuildUser)Context.User).GuildPermissions.BanMembers ? Warnlog(user.Id) : Task.CompletedTask;
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
[Priority(3)]
public Task Warnlog(int page, ulong userId)
=> InternalWarnlog(userId, page - 1);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
[Priority(2)]
public Task Warnlog(ulong userId)
=> InternalWarnlog(userId, 0);
@ -191,6 +195,39 @@ namespace NadekoBot.Modules.Administration
await Context.Channel.EmbedAsync(embed);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]
public async Task WarnlogAll(int page = 1)
{
if (--page < 0)
return;
IGrouping<ulong, Warning>[] warnings;
using (var uow = _db.UnitOfWork)
{
warnings = uow.Warnings.GetForGuild(Context.Guild.Id).GroupBy(x => x.UserId).ToArray();
}
await Context.Channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client, page, async (curPage) =>
{
var ws = await Task.WhenAll(warnings.Skip(curPage * 15)
.Take(15)
.ToArray()
.Select(async x =>
{
var all = x.Count();
var forgiven = x.Count(y => y.Forgiven);
var total = all - forgiven;
return ((await Context.Guild.GetUserAsync(x.Key))?.ToString() ?? x.Key.ToString()) + $" | {total} ({all} - {forgiven})";
}));
return new EmbedBuilder()
.WithTitle(GetText("warnings_list"))
.WithDescription(string.Join("\n", ws));
}, warnings.Length / 15);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.BanMembers)]

View File

@ -17,10 +17,10 @@ namespace NadekoBot.Modules.CustomReactions
private readonly IBotCredentials _creds;
private readonly DbService _db;
private readonly CustomReactionsService _crs;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public CustomReactions(IBotCredentials creds, DbService db, CustomReactionsService crs,
DiscordShardedClient client)
DiscordSocketClient client)
{
_creds = creds;
_db = db;
@ -79,7 +79,7 @@ namespace NadekoBot.Modules.CustomReactions
.WithTitle(GetText("new_cust_react"))
.WithDescription($"#{cr.Id}")
.AddField(efb => efb.WithName(GetText("trigger")).WithValue(key))
.AddField(efb => efb.WithName(GetText("response")).WithValue(message))
.AddField(efb => efb.WithName(GetText("response")).WithValue(message.Length > 1024 ? GetText("redacted_too_long") : message))
).ConfigureAwait(false);
}

View File

@ -22,12 +22,12 @@ namespace NadekoBot.Modules.Gambling
{
private readonly BotConfig _bc;
private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public static ConcurrentDictionary<ulong, AnimalRace> AnimalRaces { get; } = new ConcurrentDictionary<ulong, AnimalRace>();
public AnimalRacing(BotConfig bc, CurrencyService cs, DiscordShardedClient client)
public AnimalRacing(BotConfig bc, CurrencyService cs, DiscordSocketClient client)
{
_bc = bc;
_cs = cs;
@ -82,14 +82,14 @@ namespace NadekoBot.Modules.Gambling
private readonly ITextChannel _raceChannel;
private readonly BotConfig _bc;
private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly ILocalization _localization;
private readonly NadekoStrings _strings;
public bool Started { get; private set; }
public AnimalRace(ulong serverId, ITextChannel channel, string prefix, BotConfig bc,
CurrencyService cs, DiscordShardedClient client, ILocalization localization,
CurrencyService cs, DiscordSocketClient client, ILocalization localization,
NadekoStrings strings)
{
_prefix = prefix;

View File

@ -34,11 +34,11 @@ namespace NadekoBot.Modules.Gambling
.ToArray();
private string _secretCode = string.Empty;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly BotConfig _bc;
private readonly CurrencyService _cs;
public CurrencyEvents(DiscordShardedClient client, BotConfig bc, CurrencyService cs)
public CurrencyEvents(DiscordSocketClient client, BotConfig bc, CurrencyService cs)
{
_client = client;
_bc = bc;
@ -151,19 +151,21 @@ namespace NadekoBot.Modules.Gambling
{
private readonly ConcurrentHashSet<ulong> _flowerReactionAwardedUsers = new ConcurrentHashSet<ulong>();
private readonly Logger _log;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly CurrencyService _cs;
private readonly SocketSelfUser _botUser;
private IUserMessage StartingMessage { get; set; }
private CancellationTokenSource Source { get; }
private CancellationToken CancelToken { get; }
public FlowerReactionEvent(DiscordShardedClient client, CurrencyService cs)
public FlowerReactionEvent(DiscordSocketClient client, CurrencyService cs)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;
_cs = cs;
_botUser = client.CurrentUser;
Source = new CancellationTokenSource();
CancelToken = Source.Token;
}
@ -208,6 +210,9 @@ namespace NadekoBot.Modules.Gambling
{
try
{
if (r.UserId == _botUser.Id)
return;
if (r.Emote.Name == "🌸" && r.User.IsSpecified && ((DateTime.UtcNow - r.User.Value.CreatedAt).TotalDays > 5) && _flowerReactionAwardedUsers.Add(r.User.Value.Id))
{
await _cs.AddAsync(r.User.Value, "Flower Reaction Event", amount, false)

View File

@ -22,7 +22,7 @@ namespace NadekoBot.Modules.Gambling
private readonly BotConfig _bc;
private readonly DbService _db;
private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public enum Role
{
@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Gambling
List
}
public FlowerShop(BotConfig bc, DbService db, CurrencyService cs, DiscordShardedClient client)
public FlowerShop(BotConfig bc, DbService db, CurrencyService cs, DiscordSocketClient client)
{
_db = db;
_bc = bc;
@ -154,7 +154,7 @@ namespace NadekoBot.Modules.Gambling
}
try
{
await (await Context.User.CreateDMChannelAsync())
await (await Context.User.GetOrCreateDMChannelAsync())
.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithTitle(GetText("shop_purchase", Context.Guild.Name))
.AddField(efb => efb.WithName(GetText("item")).WithValue(item.Text).WithIsInline(false))

View File

@ -20,12 +20,12 @@ namespace NadekoBot.Modules.Games
[Group]
public class Acropobia : NadekoSubmodule
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
//channelId, game
public static ConcurrentDictionary<ulong, AcrophobiaGame> AcrophobiaGames { get; } = new ConcurrentDictionary<ulong, AcrophobiaGame>();
public Acropobia(DiscordShardedClient client)
public Acropobia(DiscordSocketClient client)
{
_client = client;
}
@ -86,10 +86,10 @@ namespace NadekoBot.Modules.Games
//text, votes
private readonly ConcurrentDictionary<string, int> _votes = new ConcurrentDictionary<string, int>();
private readonly Logger _log;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings;
public AcrophobiaGame(DiscordShardedClient client, NadekoStrings strings, ITextChannel channel, int time)
public AcrophobiaGame(DiscordSocketClient client, NadekoStrings strings, ITextChannel channel, int time)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;

View File

@ -56,7 +56,7 @@ namespace NadekoBot.Modules.Games.Hangman
public class HangmanGame: IDisposable
{
private readonly Logger _log;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public IMessageChannel GameChannel { get; }
public HashSet<char> Guesses { get; } = new HashSet<char>();
@ -82,7 +82,7 @@ namespace NadekoBot.Modules.Games.Hangman
public event Action<HangmanGame> OnEnded;
public HangmanGame(DiscordShardedClient client, IMessageChannel channel, string type)
public HangmanGame(DiscordSocketClient client, IMessageChannel channel, string type)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;
@ -109,8 +109,10 @@ namespace NadekoBot.Modules.Games.Hangman
var embed = new EmbedBuilder().WithTitle("Hangman Game")
.WithDescription(toSend)
.AddField(efb => efb.WithName("It was").WithValue(Term.Word))
.WithImageUrl(Term.ImageUrl)
.WithFooter(efb => efb.WithText(string.Join(" ", Guesses)));
if(Uri.IsWellFormedUriString(Term.ImageUrl, UriKind.Absolute))
embed.WithImageUrl(Term.ImageUrl);
if (Errors >= MaxErrors)
await GameChannel.EmbedAsync(embed.WithErrorColor()).ConfigureAwait(false);
else

View File

@ -15,9 +15,9 @@ namespace NadekoBot.Modules.Games
[Group]
public class HangmanCommands : NadekoSubmodule
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public HangmanCommands(DiscordShardedClient client)
public HangmanCommands(DiscordSocketClient client)
{
_client = client;
}
@ -25,12 +25,14 @@ namespace NadekoBot.Modules.Games
//channelId, game
public static ConcurrentDictionary<ulong, HangmanGame> HangmanGames { get; } = new ConcurrentDictionary<ulong, HangmanGame>();
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Hangmanlist()
{
await Context.Channel.SendConfirmAsync(Format.Code(GetText("hangman_types", Prefix)) + "\n" + string.Join(", ", HangmanTermPool.data.Keys));
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task Hangman([Remainder]string type = "All")
{
var hm = new HangmanGame(_client, Context.Channel, type);
@ -59,6 +61,17 @@ namespace NadekoBot.Modules.Games
await Context.Channel.SendConfirmAsync(GetText("hangman_game_started"), hm.ScrambledWord + "\n" + hm.GetHangman());
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
public async Task HangmanStop()
{
if (HangmanGames.TryRemove(Context.Channel.Id, out HangmanGame throwaway))
{
throwaway.Dispose();
await ReplyConfirmLocalized("hangman_stopped").ConfigureAwait(false);
}
}
}
}
}

View File

@ -20,13 +20,13 @@ namespace NadekoBot.Modules.Games.Models
public bool IsActive { get; private set; }
private readonly Stopwatch sw;
private readonly List<ulong> finishedUserIds;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly GamesService _games;
private readonly string _prefix;
private Logger _log { get; }
public TypingGame(GamesService games, DiscordShardedClient client, ITextChannel channel, string prefix) //kek@prefix
public TypingGame(GamesService games, DiscordSocketClient client, ITextChannel channel, string prefix) //kek@prefix
{
_log = LogManager.GetCurrentClassLogger();
_games = games;

View File

@ -116,7 +116,7 @@ namespace NadekoBot.Modules.Games
bool enabled;
using (var uow = _db.UnitOfWork)
{
var guildConfig = uow.GuildConfigs.For(channel.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
var guildConfig = uow.GuildConfigs.For(channel.Guild.Id, set => set.Include(gc => gc.GenerateCurrencyChannelIds));
var toAdd = new GCChannelId() { ChannelId = channel.Id };
if (!guildConfig.GenerateCurrencyChannelIds.Contains(toAdd))

View File

@ -13,10 +13,10 @@ namespace NadekoBot.Modules.Games
[Group]
public class PollCommands : NadekoSubmodule
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly PollService _polls;
public PollCommands(DiscordShardedClient client, PollService polls)
public PollCommands(DiscordSocketClient client, PollService polls)
{
_client = client;
_polls = polls;
@ -26,13 +26,7 @@ namespace NadekoBot.Modules.Games
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public Task Poll([Remainder] string arg = null)
=> InternalStartPoll(arg, false);
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
[RequireContext(ContextType.Guild)]
public Task PublicPoll([Remainder] string arg = null)
=> InternalStartPoll(arg, true);
=> InternalStartPoll(arg);
[NadekoCommand, Usage, Description, Aliases]
[RequireUserPermission(GuildPermission.ManageMessages)]
@ -45,9 +39,9 @@ namespace NadekoBot.Modules.Games
await Context.Channel.EmbedAsync(poll.GetStats(GetText("current_poll_results")));
}
private async Task InternalStartPoll(string arg, bool isPublic = false)
private async Task InternalStartPoll(string arg)
{
if(await _polls.StartPoll((ITextChannel)Context.Channel, Context.Message, arg, isPublic) == false)
if(await _polls.StartPoll((ITextChannel)Context.Channel, Context.Message, arg) == false)
await ReplyErrorLocalized("poll_already_running").ConfigureAwait(false);
}

View File

@ -20,9 +20,9 @@ namespace NadekoBot.Modules.Games
{
public static ConcurrentDictionary<ulong, TypingGame> RunningContests = new ConcurrentDictionary<ulong, TypingGame>();
private readonly GamesService _games;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public SpeedTypingCommands(DiscordShardedClient client, GamesService games)
public SpeedTypingCommands(DiscordSocketClient client, GamesService games)
{
_games = games;
_client = client;

View File

@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Games
private static readonly Dictionary<ulong, TicTacToe> _games = new Dictionary<ulong, TicTacToe>();
private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1);
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public TicTacToeCommands(DiscordShardedClient client)
public TicTacToeCommands(DiscordSocketClient client)
{
_client = client;
}
@ -87,9 +87,9 @@ namespace NadekoBot.Modules.Games
private IUserMessage _previousMessage;
private Timer _timeoutTimer;
private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public TicTacToe(NadekoStrings strings, DiscordShardedClient client, ITextChannel channel, IGuildUser firstUser)
public TicTacToe(NadekoStrings strings, DiscordSocketClient client, ITextChannel channel, IGuildUser firstUser)
{
_channel = channel;
_strings = strings;

View File

@ -20,7 +20,7 @@ namespace NadekoBot.Modules.Games.Trivia
private readonly SemaphoreSlim _guessLock = new SemaphoreSlim(1, 1);
private readonly Logger _log;
private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly BotConfig _bc;
private readonly CurrencyService _cs;
@ -43,7 +43,7 @@ namespace NadekoBot.Modules.Games.Trivia
public int WinRequirement { get; }
public TriviaGame(NadekoStrings strings, DiscordShardedClient client, BotConfig bc,
public TriviaGame(NadekoStrings strings, DiscordSocketClient client, BotConfig bc,
CurrencyService cs, IGuild guild, ITextChannel channel,
bool showHints, int winReq, bool isPokemon)
{
@ -89,8 +89,9 @@ namespace NadekoBot.Modules.Games.Trivia
questionEmbed = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("trivia_game"))
.AddField(eab => eab.WithName(GetText("category")).WithValue(CurrentQuestion.Category))
.AddField(eab => eab.WithName(GetText("question")).WithValue(CurrentQuestion.Question))
.WithImageUrl(CurrentQuestion.ImageUrl);
.AddField(eab => eab.WithName(GetText("question")).WithValue(CurrentQuestion.Question));
if (Uri.IsWellFormedUriString(CurrentQuestion.ImageUrl, UriKind.Absolute))
questionEmbed.WithImageUrl(CurrentQuestion.ImageUrl);
questionMessage = await Channel.EmbedAsync(questionEmbed).ConfigureAwait(false);
}
@ -145,11 +146,13 @@ namespace NadekoBot.Modules.Games.Trivia
{
try
{
await Channel.EmbedAsync(new EmbedBuilder().WithErrorColor()
var embed = new EmbedBuilder().WithErrorColor()
.WithTitle(GetText("trivia_game"))
.WithDescription(GetText("trivia_times_up", Format.Bold(CurrentQuestion.Answer)))
.WithImageUrl(CurrentQuestion.AnswerImageUrl))
.ConfigureAwait(false);
.WithDescription(GetText("trivia_times_up", Format.Bold(CurrentQuestion.Answer)));
if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute))
embed.WithImageUrl(CurrentQuestion.AnswerImageUrl);
await Channel.EmbedAsync(embed).ConfigureAwait(false);
}
catch (Exception ex)
{
@ -215,13 +218,14 @@ namespace NadekoBot.Modules.Games.Trivia
ShouldStopGame = true;
try
{
await Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
var embedS = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("trivia_game"))
.WithDescription(GetText("trivia_win",
guildUser.Mention,
Format.Bold(CurrentQuestion.Answer)))
.WithImageUrl(CurrentQuestion.AnswerImageUrl))
.ConfigureAwait(false);
Format.Bold(CurrentQuestion.Answer)));
if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute))
embedS.WithImageUrl(CurrentQuestion.AnswerImageUrl);
await Channel.EmbedAsync(embedS).ConfigureAwait(false);
}
catch
{
@ -232,12 +236,12 @@ namespace NadekoBot.Modules.Games.Trivia
await _cs.AddAsync(guildUser, "Won trivia", reward, true).ConfigureAwait(false);
return;
}
await Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
var embed = new EmbedBuilder().WithOkColor()
.WithTitle(GetText("trivia_game"))
.WithDescription(GetText("trivia_guess", guildUser.Mention, Format.Bold(CurrentQuestion.Answer)))
.WithImageUrl(CurrentQuestion.AnswerImageUrl))
.ConfigureAwait(false);
.WithDescription(GetText("trivia_guess", guildUser.Mention, Format.Bold(CurrentQuestion.Answer)));
if (Uri.IsWellFormedUriString(CurrentQuestion.AnswerImageUrl, UriKind.Absolute))
embed.WithImageUrl(CurrentQuestion.AnswerImageUrl);
await Channel.EmbedAsync(embed).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
});

View File

@ -18,12 +18,12 @@ namespace NadekoBot.Modules.Games
public class TriviaCommands : NadekoSubmodule
{
private readonly CurrencyService _cs;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly BotConfig _bc;
public static ConcurrentDictionary<ulong, TriviaGame> RunningTrivias { get; } = new ConcurrentDictionary<ulong, TriviaGame>();
public TriviaCommands(DiscordShardedClient client, BotConfig bc, CurrencyService cs)
public TriviaCommands(DiscordSocketClient client, BotConfig bc, CurrencyService cs)
{
_cs = cs;
_client = client;

View File

@ -96,7 +96,7 @@ namespace NadekoBot.Modules.Help
if (com == null)
{
IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)Context.User).CreateDMChannelAsync() : channel;
IMessageChannel ch = channel is ITextChannel ? await ((IGuildUser)Context.User).GetOrCreateDMChannelAsync() : channel;
await ch.SendMessageAsync(HelpString).ConfigureAwait(false);
return;
}

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@ using System.Xml;
using System.Threading;
using System.Collections.Concurrent;
using NadekoBot.Services.Searches;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.NSFW
{
@ -28,29 +29,12 @@ namespace NadekoBot.Modules.NSFW
private async Task InternalHentai(IMessageChannel channel, string tag, bool noError)
{
tag = tag?.Trim() ?? "";
tag = "rating%3Aexplicit+" + tag;
var rng = new NadekoRandom();
var provider = Task.FromResult("");
switch (rng.Next(0, 4))
{
case 0:
provider = GetDanbooruImageLink(tag);
break;
case 1:
provider = GetGelbooruImageLink(tag);
break;
case 2:
provider = GetKonachanImageLink(tag);
break;
case 3:
provider = GetYandereImageLink(tag);
break;
}
var link = await provider.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(link))
var arr = Enum.GetValues(typeof(DapiSearchType));
var type = (DapiSearchType)arr.GetValue(new NadekoRandom().Next(2, arr.Length));
var img = await _service.DapiSearch(tag, type, Context.Guild?.Id, true).ConfigureAwait(false);
if (img == null)
{
if (!noError)
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
@ -58,8 +42,8 @@ namespace NadekoBot.Modules.NSFW
}
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithImageUrl(link)
.WithDescription($"[{GetText("tag")}: {tag}]({link})"))
.WithImageUrl(img.FileUrl)
.WithDescription($"[{GetText("tag")}: {tag}]({img})"))
.ConfigureAwait(false);
}
@ -112,110 +96,58 @@ namespace NadekoBot.Modules.NSFW
interval,
string.Join(", ", tagsArr)).ConfigureAwait(false);
}
#endif
[NadekoCommand, Usage, Description, Aliases]
public async Task HentaiBomb([Remainder] string tag = null)
{
if (!_hentaiBombBlacklist.Add(Context.User.Id))
if (!_hentaiBombBlacklist.Add(Context.Guild?.Id ?? Context.User.Id))
return;
try
{
tag = tag?.Trim() ?? "";
tag = "rating%3Aexplicit+" + tag;
var images = await Task.WhenAll(_service.DapiSearch(tag, DapiSearchType.Gelbooru, Context.Guild?.Id, true),
_service.DapiSearch(tag, DapiSearchType.Danbooru, Context.Guild?.Id, true),
_service.DapiSearch(tag, DapiSearchType.Konachan, Context.Guild?.Id, true),
_service.DapiSearch(tag, DapiSearchType.Yandere, Context.Guild?.Id, true)).ConfigureAwait(false);
var links = await Task.WhenAll(GetGelbooruImageLink(tag),
GetDanbooruImageLink(tag),
GetKonachanImageLink(tag),
GetYandereImageLink(tag)).ConfigureAwait(false);
var linksEnum = links?.Where(l => l != null).ToArray();
if (links == null || !linksEnum.Any())
var linksEnum = images?.Where(l => l != null).ToArray();
if (images == null || !linksEnum.Any())
{
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
return;
}
await Context.Channel.SendMessageAsync(string.Join("\n\n", linksEnum)).ConfigureAwait(false);
await Context.Channel.SendMessageAsync(string.Join("\n\n", linksEnum.Select(x => x.FileUrl))).ConfigureAwait(false);
}
finally
{
await Task.Delay(5000).ConfigureAwait(false);
_hentaiBombBlacklist.TryRemove(Context.User.Id);
_hentaiBombBlacklist.TryRemove(Context.Guild?.Id ?? Context.User.Id);
}
}
#endif
[NadekoCommand, Usage, Description, Aliases]
public Task Yandere([Remainder] string tag = null)
=> InternalDapiCommand(tag, DapiSearchType.Yandere);
=> InternalDapiCommand(tag, DapiSearchType.Yandere, false);
[NadekoCommand, Usage, Description, Aliases]
public Task Konachan([Remainder] string tag = null)
=> InternalDapiCommand(tag, DapiSearchType.Konachan);
=> InternalDapiCommand(tag, DapiSearchType.Konachan, false);
[NadekoCommand, Usage, Description, Aliases]
public async Task E621([Remainder] string tag = null)
{
tag = tag?.Trim() ?? "";
var url = await GetE621ImageLink(tag).ConfigureAwait(false);
if (url == null)
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(Context.User.Mention + " " + tag)
.WithImageUrl(url)
.WithFooter(efb => efb.WithText("e621")))
.ConfigureAwait(false);
}
public Task E621([Remainder] string tag = null)
=> InternalDapiCommand(tag, DapiSearchType.E621, false);
[NadekoCommand, Usage, Description, Aliases]
public Task Rule34([Remainder] string tag = null)
=> InternalDapiCommand(tag, DapiSearchType.Rule34);
=> InternalDapiCommand(tag, DapiSearchType.Rule34, false);
[NadekoCommand, Usage, Description, Aliases]
public async Task Danbooru([Remainder] string tag = null)
{
tag = tag?.Trim() ?? "";
var url = await GetDanbooruImageLink(tag).ConfigureAwait(false);
if (url == null)
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription(Context.User.Mention + " " + tag)
.WithImageUrl(url)
.WithFooter(efb => efb.WithText("Danbooru")))
.ConfigureAwait(false);
}
public static Task<string> GetDanbooruImageLink(string tag) => Task.Run(async () =>
{
try
{
using (var http = new HttpClient())
{
http.AddFakeHeaders();
var data = await http.GetStreamAsync("https://danbooru.donmai.us/posts.xml?limit=100&tags=" + tag).ConfigureAwait(false);
var doc = new XmlDocument();
doc.Load(data);
var nodes = doc.GetElementsByTagName("file-url");
var node = nodes[new NadekoRandom().Next(0, nodes.Count)];
return "https://danbooru.donmai.us" + node.InnerText;
}
}
catch
{
return null;
}
});
public Task Danbooru([Remainder] string tag = null)
=> InternalDapiCommand(tag, DapiSearchType.Danbooru, false);
[NadekoCommand, Usage, Description, Aliases]
public Task Gelbooru([Remainder] string tag = null)
=> InternalDapiCommand(tag, DapiSearchType.Gelbooru);
=> InternalDapiCommand(tag, DapiSearchType.Gelbooru, false);
[NadekoCommand, Usage, Description, Aliases]
public async Task Boobs()
@ -253,52 +185,16 @@ namespace NadekoBot.Modules.NSFW
}
}
public static Task<string> GetE621ImageLink(string tag) => Task.Run(async () =>
public async Task InternalDapiCommand(string tag, DapiSearchType type, bool forceExplicit)
{
try
{
using (var http = new HttpClient())
{
http.AddFakeHeaders();
var data = await http.GetStreamAsync("http://e621.net/post/index.xml?tags=" + tag).ConfigureAwait(false);
var doc = new XmlDocument();
doc.Load(data);
var nodes = doc.GetElementsByTagName("file_url");
var imgObj = await _service.DapiSearch(tag, type, Context.Guild?.Id, forceExplicit).ConfigureAwait(false);
var node = nodes[new NadekoRandom().Next(0, nodes.Count)];
return node.InnerText;
}
}
catch
{
return null;
}
});
public Task<string> GetRule34ImageLink(string tag) =>
_service.DapiSearch(tag, DapiSearchType.Rule34);
public Task<string> GetYandereImageLink(string tag) =>
_service.DapiSearch(tag, DapiSearchType.Yandere);
public Task<string> GetKonachanImageLink(string tag) =>
_service.DapiSearch(tag, DapiSearchType.Konachan);
public Task<string> GetGelbooruImageLink(string tag) =>
_service.DapiSearch(tag, DapiSearchType.Gelbooru);
public async Task InternalDapiCommand(string tag, DapiSearchType type)
{
tag = tag?.Trim() ?? "";
var url = await _service.DapiSearch(tag, type).ConfigureAwait(false);
if (url == null)
if (imgObj == null)
await ReplyErrorLocalized("not_found").ConfigureAwait(false);
else
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription($"{Context.User} [{tag}]({url}) ")
.WithImageUrl(url)
.WithDescription($"{Context.User} [{tag ?? "url"}]({imgObj}) ")
.WithImageUrl(imgObj.FileUrl)
.WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
}
}

View File

@ -32,7 +32,7 @@ namespace NadekoBot.Modules
_log = LogManager.GetCurrentClassLogger();
}
protected override void BeforeExecute()
protected override void BeforeExecute(CommandInfo cmd)
{
_cultureInfo = _localization.GetCultureInfo(Context.Guild?.Id);
}
@ -87,12 +87,11 @@ namespace NadekoBot.Modules
return Context.Channel.SendConfirmAsync(Context.User.Mention + " " + text);
}
// todo maybe make this generic and use
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType);
// TypeConverter typeConverter = TypeDescriptor.GetConverter(propType); ?
public async Task<string> GetUserInputAsync(ulong userId, ulong channelId)
{
var userInputTask = new TaskCompletionSource<string>();
var dsc = (DiscordShardedClient)Context.Client;
var dsc = (DiscordSocketClient)Context.Client;
try
{
dsc.MessageReceived += MessageReceived;

View File

@ -195,7 +195,7 @@ namespace NadekoBot.Modules.Permissions
var fws = fwHash.ToArray();
await channel.SendPaginatedConfirmAsync((DiscordShardedClient)Context.Client,
await channel.SendPaginatedConfirmAsync((DiscordSocketClient)Context.Client,
page,
(curPage) =>
new EmbedBuilder()

View File

@ -2,9 +2,11 @@
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
@ -29,26 +31,30 @@ namespace NadekoBot.Modules.Searches
"If you consider playing teemo, do it. If you consider teemo, you deserve him.",
"Doesn't matter what you ban really. Enemy will ban your main and you will lose." };
private static readonly Lazy<Dictionary<int, string>> champData = new Lazy<Dictionary<int, string>>(() =>
((IDictionary<string, JToken>)JObject.Parse(File.ReadAllText("data/lolchamps.json")))
.ToDictionary(x => (int)x.Value["id"], x => x.Value["name"].ToString()), true);
[NadekoCommand, Usage, Description, Aliases]
public async Task Lolban()
{
const int showCount = 8;
//http://api.champion.gg/stats/champs/mostBanned?api_key=YOUR_API_TOKEN&page=1&limit=2
//const int showCount = 8;
try
{
using (var http = new HttpClient())
{
var data = JObject.Parse(await http.GetStringAsync($"http://api.champion.gg/stats/champs/mostBanned?" +
$"api_key={_creds.LoLApiKey}&page=1&" +
$"limit={showCount}")
.ConfigureAwait(false))["data"] as JArray;
var dataList = data.Distinct(new ChampionNameComparer()).Take(showCount).ToList();
var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline(GetText("x_most_banned_champs",dataList.Count)));
foreach (var champ in dataList)
var data = JArray.Parse(await http.GetStringAsync($"http://api.champion.gg/v2/champions?champData=general&limit=200&api_key={_creds.LoLApiKey}"));
var champs = data.OrderByDescending(x => (double)x["banRate"]).Distinct(x => x["championId"]).Take(6);
//_log.Info(string.Join("\n", champs.Select(x => x["championId"] + " | " + x["banRate"] + " | " + x["normalized"]["banRate"])));
var eb = new EmbedBuilder().WithOkColor().WithTitle(Format.Underline(GetText("x_most_banned_champs", champs.Count())));
foreach (var champ in champs)
{
var champ1 = champ;
eb.AddField(efb => efb.WithName(champ1["name"].ToString()).WithValue(champ1["general"]["banRate"] + "%").WithIsInline(true));
var lChamp = champ;
if (!champData.Value.TryGetValue((int)champ["championId"], out var champName))
champName = "UNKNOWN";
eb.AddField(efb => efb.WithName(champName).WithValue(((double)lChamp["banRate"] * 100).ToString("F2") + "%").WithIsInline(true));
}
await Context.Channel.EmbedAsync(eb, Format.Italics(trashTalk[new NadekoRandom().Next(0, trashTalk.Length)])).ConfigureAwait(false);
@ -173,7 +179,6 @@ namespace NadekoBot.Modules.Searches
// .FirstOrDefault(jt => jt["role"].ToString() == role)?["general"];
// if (general == null)
// {
// //Console.WriteLine("General is null.");
// return;
// }
// //get build data for this role
@ -309,7 +314,6 @@ namespace NadekoBot.Modules.Searches
// }
// catch (Exception ex)
// {
// //Console.WriteLine(ex);
// await channel.SendMessageAsync("💢 Failed retreiving data for that champion.").ConfigureAwait(false);
// }
// });

View File

@ -62,8 +62,8 @@ namespace NadekoBot.Modules.Searches
.AddField(fb => fb.WithName(GetText("quick_wins")).WithValue(qp.OverallStats.wins.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_wins")).WithValue(compet.OverallStats.wins.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_loses")).WithValue(compet.OverallStats.losses.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_played")).WithValue(compet.OverallStats.games.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_rank")).WithValue(compet.OverallStats.comprank.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_played")).WithValue(compet.OverallStats.games.ToString() ?? "-").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_rank")).WithValue(compet.OverallStats.comprank?.ToString() ?? "-").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("compet_playtime")).WithValue(compet.GameStats.timePlayed + "hrs").WithIsInline(true))
.AddField(fb => fb.WithName(GetText("quick_playtime")).WithValue(qp.GameStats.timePlayed.ToString("F1") + "hrs").WithIsInline(true))
.WithColor(NadekoBot.OkColor);

View File

@ -21,6 +21,7 @@ using NadekoBot.Attributes;
using Discord.Commands;
using ImageSharp;
using NadekoBot.Services.Searches;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.Searches
{
@ -666,7 +667,7 @@ namespace NadekoBot.Modules.Searches
str += new NadekoRandom().Next();
foreach (var usr in allUsrsArray)
{
await (await usr.CreateDMChannelAsync()).SendConfirmAsync(str).ConfigureAwait(false);
await (await usr.GetOrCreateDMChannelAsync()).SendConfirmAsync(str).ConfigureAwait(false);
}
}
@ -791,14 +792,14 @@ namespace NadekoBot.Modules.Searches
tag = tag?.Trim() ?? "";
var url = await _searches.DapiSearch(tag, type).ConfigureAwait(false);
var imgObj = await _searches.DapiSearch(tag, type, Context.Guild?.Id).ConfigureAwait(false);
if (url == null)
if (imgObj == null)
await channel.SendErrorAsync(umsg.Author.Mention + " " + GetText("no_results"));
else
await channel.EmbedAsync(new EmbedBuilder().WithOkColor()
.WithDescription($"{umsg.Author.Mention} [{tag}]({url})")
.WithImageUrl(url)
.WithDescription($"{umsg.Author.Mention} [{tag ?? "url"}]({imgObj.FileUrl})")
.WithImageUrl(imgObj.FileUrl)
.WithFooter(efb => efb.WithText(type.ToString()))).ConfigureAwait(false);
}

View File

@ -21,9 +21,9 @@ namespace NadekoBot.Modules.Utility
{
private readonly CommandMapService _service;
private readonly DbService _db;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public CommandMapCommands(CommandMapService service, DbService db, DiscordShardedClient client)
public CommandMapCommands(CommandMapService service, DbService db, DiscordSocketClient client)
{
_service = service;
_db = db;

View File

@ -1,64 +0,0 @@
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Utility;
using System.Collections.Concurrent;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility
{
public partial class Utility
{
[Group]
public class CrossServerTextChannel : NadekoSubmodule
{
private readonly CrossServerTextService _service;
public CrossServerTextChannel(CrossServerTextService service)
{
_service = service;
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task Scsc()
{
var token = new NadekoRandom().Next();
var set = new ConcurrentHashSet<ITextChannel>();
if (_service.Subscribers.TryAdd(token, set))
{
set.Add((ITextChannel) Context.Channel);
await ((IGuildUser) Context.User).SendConfirmAsync(GetText("csc_token"), token.ToString())
.ConfigureAwait(false);
}
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageGuild)]
public async Task Jcsc(int token)
{
ConcurrentHashSet<ITextChannel> set;
if (!_service.Subscribers.TryGetValue(token, out set))
return;
set.Add((ITextChannel) Context.Channel);
await ReplyConfirmLocalized("csc_join").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageGuild)]
public async Task Lcsc()
{
foreach (var subscriber in _service.Subscribers)
{
subscriber.Value.TryRemove((ITextChannel) Context.Channel);
}
await ReplyConfirmLocalized("csc_leave").ConfigureAwait(false);
}
}
}
}

View File

@ -16,10 +16,10 @@ namespace NadekoBot.Modules.Utility
[Group]
public class InfoCommands : NadekoSubmodule
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly IStatsService _stats;
public InfoCommands(DiscordShardedClient client, IStatsService stats, CommandHandler ch)
public InfoCommands(DiscordSocketClient client, IStatsService stats, CommandHandler ch)
{
_client = client;
_stats = stats;
@ -59,8 +59,9 @@ namespace NadekoBot.Modules.Utility
.AddField(fb => fb.WithName(GetText("region")).WithValue(guild.VoiceRegionId.ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("roles")).WithValue((guild.Roles.Count - 1).ToString()).WithIsInline(true))
.AddField(fb => fb.WithName(GetText("features")).WithValue(features).WithIsInline(true))
.WithImageUrl(guild.IconUrl)
.WithColor(NadekoBot.OkColor);
if (Uri.IsWellFormedUriString(guild.IconUrl, UriKind.Absolute))
embed.WithImageUrl(guild.IconUrl);
if (guild.Emotes.Any())
{
embed.AddField(fb => fb.WithName(GetText("custom_emojis") + $"({guild.Emotes.Count})").WithValue(string.Join(" ", guild.Emotes.Shuffle().Take(20).Select(e => $"{e.Name} <:{e.Name}:{e.Id}>"))));

View File

@ -32,18 +32,23 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
[RequireContext(ContextType.DM)]
public async Task PatreonRewardsReload()
{
await _patreon.LoadPledges().ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
return;
await _patreon.RefreshPledges(true).ConfigureAwait(false);
await Context.Channel.SendConfirmAsync("👌").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.DM)]
public async Task ClaimPatreonRewards()
{
if (string.IsNullOrWhiteSpace(_creds.PatreonAccessToken))
return;
if (DateTime.UtcNow.Day < 5)
{
await ReplyErrorLocalized("clpa_too_early").ConfigureAwait(false);

View File

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NadekoBot.DataStructures;
using NadekoBot.DataStructures.Replacements;
namespace NadekoBot.Modules.Utility
{
@ -66,22 +67,18 @@ namespace NadekoBot.Modules.Utility
if (quote == null)
return;
CREmbed crembed;
if (CREmbed.TryParse(quote.Text, out crembed))
var rep = new ReplacementBuilder()
.WithDefault(Context)
.Build();
if (CREmbed.TryParse(quote.Text, out var crembed))
{
try
{
await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "")
rep.Replace(crembed);
await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "")
.ConfigureAwait(false);
}
catch (Exception ex)
{
_log.Warn("Sending CREmbed failed");
_log.Warn(ex);
}
return;
}
await Context.Channel.SendMessageAsync($"`#{quote.Id}` 📣 " + quote.Text.SanitizeMentions());
await Context.Channel.SendMessageAsync($"`#{quote.Id}` 📣 " + rep.Replace(quote.Text)?.SanitizeMentions());
}
[NadekoCommand, Usage, Description, Aliases]
@ -118,29 +115,28 @@ namespace NadekoBot.Modules.Utility
using (var uow = _db.UnitOfWork)
{
var qfromid = uow.Quotes.Get(id);
CREmbed crembed;
var rep = new ReplacementBuilder()
.WithDefault(Context)
.Build();
if (qfromid == null)
{
await Context.Channel.SendErrorAsync(GetText("quotes_notfound"));
}
else if (CREmbed.TryParse(qfromid.Text, out crembed))
else if (CREmbed.TryParse(qfromid.Text, out var crembed))
{
try
{
await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "")
rep.Replace(crembed);
await Context.Channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "")
.ConfigureAwait(false);
}
catch (Exception ex)
else
{
_log.Warn("Sending CREmbed failed");
_log.Warn(ex);
}
return;
await Context.Channel.SendMessageAsync($"`#{qfromid.Id}` 🗯️ " + qfromid.Keyword.ToLowerInvariant().SanitizeMentions() + ": " +
rep.Replace(qfromid.Text)?.SanitizeMentions());
}
else { await Context.Channel.SendMessageAsync($"`#{qfromid.Id}` 🗯️ " + qfromid.Keyword.ToLowerInvariant().SanitizeMentions() + ": " +
qfromid.Text.SanitizeMentions()); }
}
}

View File

@ -3,6 +3,7 @@ using Discord.Commands;
using NadekoBot.Attributes;
using NadekoBot.Extensions;
using NadekoBot.Services;
using NadekoBot.Services.Administration;
using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Utility;
using System;
@ -19,11 +20,13 @@ namespace NadekoBot.Modules.Utility
{
private readonly RemindService _service;
private readonly DbService _db;
private readonly GuildTimezoneService _tz;
public RemindCommands(RemindService service, DbService db)
public RemindCommands(RemindService service, DbService db, GuildTimezoneService tz)
{
_service = service;
_db = db;
_tz = tz;
}
public enum MeOrHere
@ -33,7 +36,7 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[Priority(1)]
[Priority(0)]
public async Task Remind(MeOrHere meorhere, string timeStr, [Remainder] string message)
{
ulong target;
@ -44,7 +47,7 @@ namespace NadekoBot.Modules.Utility
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageMessages)]
[Priority(0)]
[Priority(1)]
public async Task Remind(ITextChannel channel, string timeStr, [Remainder] string message)
{
var perms = ((IGuildUser)Context.User).GetPermissions((ITextChannel)channel);
@ -119,6 +122,7 @@ namespace NadekoBot.Modules.Utility
await uow.CompleteAsync();
}
var gTime = TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(Context.Guild.Id));
try
{
await Context.Channel.SendConfirmAsync(
@ -126,7 +130,7 @@ namespace NadekoBot.Modules.Utility
Format.Bold(!isPrivate ? $"<#{targetId}>" : Context.User.Username),
Format.Bold(message.SanitizeMentions()),
Format.Bold(output),
time, time)).ConfigureAwait(false);
gTime, gTime)).ConfigureAwait(false);
}
catch
{

View File

@ -22,10 +22,10 @@ namespace NadekoBot.Modules.Utility
public class RepeatCommands : NadekoSubmodule
{
private readonly MessageRepeaterService _service;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly DbService _db;
public RepeatCommands(MessageRepeaterService service, DiscordShardedClient client, DbService db)
public RepeatCommands(MessageRepeaterService service, DiscordSocketClient client, DbService db)
{
_service = service;
_client = client;

View File

@ -6,7 +6,6 @@ using NadekoBot.Services.Utility;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Utility
{
public partial class Utility

View File

@ -18,100 +18,26 @@ using Discord.WebSocket;
using System.Diagnostics;
using Color = Discord.Color;
using NadekoBot.Services;
using NadekoBot.DataStructures;
namespace NadekoBot.Modules.Utility
{
public partial class Utility : NadekoTopLevelModule
{
private static ConcurrentDictionary<ulong, Timer> _rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly IStatsService _stats;
private readonly IBotCredentials _creds;
private readonly NadekoBot _bot;
public Utility(DiscordShardedClient client, IStatsService stats, IBotCredentials creds)
public Utility(NadekoBot bot, DiscordSocketClient client, IStatsService stats, IBotCredentials creds)
{
_client = client;
_stats = stats;
_creds = creds;
_bot = bot;
}
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//public async Task Midorina([Remainder] string arg)
//{
// var channel = (ITextChannel)Context.Channel;
// var roleNames = arg?.Split(';');
// if (roleNames == null || roleNames.Length == 0)
// return;
// var j = 0;
// var roles = roleNames.Select(x => Context.Guild.Roles.FirstOrDefault(r => String.Compare(r.Name, x, StringComparison.OrdinalIgnoreCase) == 0))
// .Where(x => x != null)
// .Take(10)
// .ToArray();
// var rnd = new NadekoRandom();
// var reactions = new[] { "🎬", "🐧", "🌍", "🌺", "🚀", "☀", "🌲", "🍒", "🐾", "🏀" }
// .OrderBy(x => rnd.Next())
// .ToArray();
// var roleStrings = roles
// .Select(x => $"{reactions[j++]} -> {x.Name}");
// var msg = await Context.Channel.SendConfirmAsync("Pick a Role",
// string.Join("\n", roleStrings)).ConfigureAwait(false);
// for (int i = 0; i < roles.Length; i++)
// {
// try { await msg.AddReactionAsync(reactions[i]).ConfigureAwait(false); }
// catch (Exception ex) { _log.Warn(ex); }
// await Task.Delay(1000).ConfigureAwait(false);
// }
// msg.OnReaction((r) => Task.Run(async () =>
// {
// try
// {
// var usr = r.User.GetValueOrDefault() as IGuildUser;
// if (usr == null)
// return;
// var index = Array.IndexOf<string>(reactions, r.Emoji.Name);
// if (index == -1)
// return;
// await usr.RemoveRolesAsync(roles[index]);
// }
// catch (Exception ex)
// {
// _log.Warn(ex);
// }
// }), (r) => Task.Run(async () =>
// {
// try
// {
// var usr = r.User.GetValueOrDefault() as IGuildUser;
// if (usr == null)
// return;
// var index = Array.IndexOf<string>(reactions, r.Emoji.Name);
// if (index == -1)
// return;
// await usr.RemoveRolesAsync(roles[index]);
// }
// catch (Exception ex)
// {
// _log.Warn(ex);
// }
// }));
//}
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
@ -354,23 +280,30 @@ namespace NadekoBot.Modules.Utility
}
[NadekoCommand, Usage, Description, Aliases]
[Shard0Precondition]
public async Task ShardStats(int page = 1)
{
if (--page < 0)
return;
var statuses = _bot.ShardCoord.Statuses.ToArray()
.Where(x => x != null);
var status = string.Join(", ", _client.Shards.GroupBy(x => x.ConnectionState)
var status = string.Join(", ", statuses
.GroupBy(x => x.ConnectionState)
.Select(x => $"{x.Count()} {x.Key}")
.ToArray());
var allShardStrings = _client.Shards
var allShardStrings = statuses
.Select(x =>
GetText("shard_stats_txt", x.ShardId.ToString(),
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.Count.ToString())))
{
var timeDiff = DateTime.UtcNow - x.Time;
if (timeDiff > TimeSpan.FromSeconds(20))
return $"Shard #{Format.Bold(x.ShardId.ToString())} **UNRESPONSIVE** for {timeDiff.ToString(@"hh\:mm\:ss")}";
return GetText("shard_stats_txt", x.ShardId.ToString(),
Format.Bold(x.ConnectionState.ToString()), Format.Bold(x.Guilds.ToString()), timeDiff.ToString(@"hh\:mm\:ss"));
})
.ToArray();
await Context.Channel.SendPaginatedConfirmAsync(_client, page, (curPage) =>
{
@ -387,21 +320,9 @@ namespace NadekoBot.Modules.Utility
}, allShardStrings.Length / 25);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task ShardId(IGuild guild)
{
var shardId = _client.GetShardIdFor(guild);
await Context.Channel.SendConfirmAsync(shardId.ToString()).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
public async Task Stats()
{
var shardId = Context.Guild != null
? _client.GetShardIdFor(Context.Guild)
: 0;
await Context.Channel.EmbedAsync(
new EmbedBuilder().WithOkColor()
.WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}")
@ -409,7 +330,7 @@ namespace NadekoBot.Modules.Utility
.WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg"))
.AddField(efb => efb.WithName(GetText("author")).WithValue(_stats.Author).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("botid")).WithValue(_client.CurrentUser.Id.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{shardId} / {_client.Shards.Count}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("shard")).WithValue($"#{_bot.ShardId} / {_creds.TotalShards}").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("commands_ran")).WithValue(_stats.CommandsRan.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("messages")).WithValue($"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
.AddField(efb => efb.WithName(GetText("memory")).WithValue($"{_stats.Heap} MB").WithIsInline(true))
@ -417,13 +338,7 @@ namespace NadekoBot.Modules.Utility
.AddField(efb => efb.WithName(GetText("uptime")).WithValue(_stats.GetUptimeString("\n")).WithIsInline(true))
.AddField(efb => efb.WithName(GetText("presence")).WithValue(
GetText("presence_txt",
_client.Guilds.Count, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true))
#if !GLOBAL_NADEKO
//.WithFooter(efb => efb.WithText(GetText("stats_songs",
// _music.MusicPlayers.Count(mp => mp.Value.CurrentSong != null),
// _music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count))))
#endif
);
_stats.GuildCount, _stats.TextChannels, _stats.VoiceChannels)).WithIsInline(true)));
}
[NadekoCommand, Usage, Description, Aliases]

View File

@ -4,8 +4,6 @@ using Discord.WebSocket;
using NadekoBot.Services;
using NadekoBot.Services.Impl;
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using System.Linq;
using System.Reflection;
@ -27,6 +25,7 @@ using NadekoBot.Services.Utility;
using NadekoBot.Services.Help;
using System.IO;
using NadekoBot.Services.Pokemon;
using NadekoBot.DataStructures.ShardCom;
using NadekoBot.DataStructures;
using NadekoBot.Extensions;
@ -45,72 +44,113 @@ namespace NadekoBot
public static Color OkColor { get; private set; }
public static Color ErrorColor { get; private set; }
public ImmutableArray<GuildConfig> AllGuildConfigs { get; }
public ImmutableArray<GuildConfig> AllGuildConfigs { get; private set; }
public BotConfig BotConfig { get; }
public DbService Db { get; }
public CommandService CommandService { get; }
public CommandHandler CommandHandler { get; private set; }
public Localization Localization { get; }
public NadekoStrings Strings { get; }
public StatsService Stats { get; }
public Localization Localization { get; private set; }
public NadekoStrings Strings { get; private set; }
public StatsService Stats { get; private set; }
public ImagesService Images { get; }
public CurrencyService Currency { get; }
public GoogleApiService GoogleApi { get; }
public DiscordShardedClient Client { get; }
public DiscordSocketClient Client { get; }
public bool Ready { get; private set; }
public INServiceProvider Services { get; private set; }
public BotCredentials Credentials { get; }
public NadekoBot()
public int ShardId { get; }
public ShardsCoordinator ShardCoord { get; private set; }
private readonly ShardComClient _comClient;
public NadekoBot(int shardId, int parentProcessId, int? port = null)
{
SetupLogger();
if (shardId < 0)
throw new ArgumentOutOfRangeException(nameof(shardId));
ShardId = shardId;
LogSetup.SetupLogger();
_log = LogManager.GetCurrentClassLogger();
TerribleElevatedPermissionCheck();
Credentials = new BotCredentials();
port = port ?? Credentials.ShardRunPort;
_comClient = new ShardComClient(port.Value);
Db = new DbService(Credentials);
using (var uow = Db.UnitOfWork)
{
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs().ToImmutableArray();
BotConfig = uow.BotConfig.GetOrCreate();
OkColor = new Color(Convert.ToUInt32(BotConfig.OkColor, 16));
ErrorColor = new Color(Convert.ToUInt32(BotConfig.ErrorColor, 16));
}
Client = new DiscordShardedClient(new DiscordSocketConfig
Client = new DiscordSocketClient(new DiscordSocketConfig
{
MessageCacheSize = 10,
LogLevel = LogSeverity.Warning,
TotalShards = Credentials.TotalShards,
ConnectionTimeout = int.MaxValue,
TotalShards = Credentials.TotalShards,
ShardId = shardId,
AlwaysDownloadUsers = false,
});
CommandService = new CommandService(new CommandServiceConfig()
{
CaseSensitiveCommands = false,
DefaultRunMode = RunMode.Async,
DefaultRunMode = RunMode.Sync,
});
//foundation services
Localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db);
Strings = new NadekoStrings(Localization);
CommandHandler = new CommandHandler(Client, Db, BotConfig, AllGuildConfigs, CommandService, Credentials, this);
Stats = new StatsService(Client, CommandHandler, Credentials);
Images = new ImagesService();
Currency = new CurrencyService(BotConfig, Db);
GoogleApi = new GoogleApiService(Credentials);
SetupShard(shardId, parentProcessId, port.Value);
#if GLOBAL_NADEKO
Client.Log += Client_Log;
#endif
}
private void StartSendingData()
{
Task.Run(async () =>
{
while (true)
{
await _comClient.Send(new ShardComMessage()
{
ConnectionState = Client.ConnectionState,
Guilds = Client.ConnectionState == ConnectionState.Connected ? Client.Guilds.Count : 0,
ShardId = Client.ShardId,
Time = DateTime.UtcNow,
});
await Task.Delay(5000);
}
});
}
private void AddServices()
{
var startingGuildIdList = Client.Guilds.Select(x => (long)x.Id).ToList();
//this unit of work will be used for initialization of all modules too, to prevent multiple queries from running
using (var uow = Db.UnitOfWork)
{
AllGuildConfigs = uow.GuildConfigs.GetAllGuildConfigs(startingGuildIdList).ToImmutableArray();
Localization = new Localization(BotConfig.Locale, AllGuildConfigs.ToDictionary(x => x.GuildId, x => x.Locale), Db);
Strings = new NadekoStrings(Localization);
CommandHandler = new CommandHandler(Client, Db, BotConfig, AllGuildConfigs, CommandService, Credentials, this);
Stats = new StatsService(Client, CommandHandler, Credentials, ShardCoord);
var soundcloudApiService = new SoundCloudApiService(Credentials);
#region help
@ -120,18 +160,17 @@ namespace NadekoBot
//module services
//todo 90 - autodiscover, DI, and add instead of manual like this
#region utility
var crossServerTextService = new CrossServerTextService(AllGuildConfigs, Client);
var remindService = new RemindService(Client, BotConfig, Db);
var remindService = new RemindService(Client, BotConfig, Db, startingGuildIdList, uow);
var repeaterService = new MessageRepeaterService(this, Client, AllGuildConfigs);
var converterService = new ConverterService(Db);
var converterService = new ConverterService(Client, Db);
var commandMapService = new CommandMapService(AllGuildConfigs);
var patreonRewardsService = new PatreonRewardsService(Credentials, Db, Currency);
var patreonRewardsService = new PatreonRewardsService(Credentials, Db, Currency, Client);
var verboseErrorsService = new VerboseErrorsService(AllGuildConfigs, Db, CommandHandler, helpService);
var pruneService = new PruneService();
#endregion
#region permissions
var permissionsService = new PermissionService(Db, BotConfig, CommandHandler);
var permissionsService = new PermissionService(Client, Db, BotConfig, CommandHandler, Strings);
var blacklistService = new BlacklistService(BotConfig);
var cmdcdsService = new CmdCdService(AllGuildConfigs);
var filterService = new FilterService(Client, AllGuildConfigs);
@ -144,13 +183,13 @@ namespace NadekoBot
var animeSearchService = new AnimeSearchService();
#endregion
var clashService = new ClashOfClansService(Client, Db, Localization, Strings);
var musicService = new MusicService(GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs);
var crService = new CustomReactionsService(permissionsService, Db, Client, CommandHandler, BotConfig);
var clashService = new ClashOfClansService(Client, Db, Localization, Strings, uow, startingGuildIdList);
var musicService = new MusicService(Client, GoogleApi, Strings, Localization, Db, soundcloudApiService, Credentials, AllGuildConfigs);
var crService = new CustomReactionsService(permissionsService, Db, Strings, Client, CommandHandler, BotConfig, uow);
#region Games
var gamesService = new GamesService(Client, BotConfig, AllGuildConfigs, Strings, Images, CommandHandler);
var chatterBotService = new ChatterBotService(Client, permissionsService, AllGuildConfigs, CommandHandler);
var chatterBotService = new ChatterBotService(Client, permissionsService, AllGuildConfigs, CommandHandler, Strings);
var pollService = new PollService(Client, Strings);
#endregion
@ -163,18 +202,17 @@ namespace NadekoBot
var muteService = new MuteService(Client, AllGuildConfigs, Db);
var ratelimitService = new SlowmodeService(Client, AllGuildConfigs);
var protectionService = new ProtectionService(Client, AllGuildConfigs, muteService);
var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService);
var playingRotateService = new PlayingRotateService(Client, BotConfig, musicService, Db);
var gameVcService = new GameVoiceChannelService(Client, Db, AllGuildConfigs);
var autoAssignRoleService = new AutoAssignRoleService(Client, AllGuildConfigs);
var logCommandService = new LogCommandService(Client, Strings, AllGuildConfigs, Db, muteService, protectionService);
var guildTimezoneService = new GuildTimezoneService(AllGuildConfigs, Db);
var guildTimezoneService = new GuildTimezoneService(Client, AllGuildConfigs, Db);
var logCommandService = new LogCommandService(Client, Strings, AllGuildConfigs, Db, muteService, protectionService, guildTimezoneService);
#endregion
#region pokemon
var pokemonService = new PokemonService();
#endregion
//initialize Services
Services = new NServiceProvider.ServiceProviderBuilder()
.Add<ILocalization>(Localization)
@ -185,13 +223,12 @@ namespace NadekoBot
.Add<IBotCredentials>(Credentials)
.Add<CommandService>(CommandService)
.Add<NadekoStrings>(Strings)
.Add<DiscordShardedClient>(Client)
.Add<DiscordSocketClient>(Client)
.Add<BotConfig>(BotConfig)
.Add<CurrencyService>(Currency)
.Add<CommandHandler>(CommandHandler)
.Add<DbService>(Db)
//modules
.Add(crossServerTextService)
.Add(commandMapService)
.Add(remindService)
.Add(repeaterService)
@ -228,6 +265,7 @@ namespace NadekoBot
.Add(filterService)
.Add(globalPermsService)
.Add<PokemonService>(pokemonService)
.Add<NadekoBot>(this)
.Build();
CommandHandler.AddServices(Services);
@ -240,36 +278,76 @@ namespace NadekoBot
CommandService.AddTypeReader<ModuleOrCrInfo>(new ModuleOrCrTypeReader(CommandService));
CommandService.AddTypeReader<IGuild>(new GuildTypeReader(Client));
CommandService.AddTypeReader<GuildDateTime>(new GuildDateTimeTypeReader(guildTimezoneService));
}
}
private async Task LoginAsync(string token)
{
_log.Info("Logging in...");
var clientReady = new TaskCompletionSource<bool>();
Task SetClientReady()
{
var _ = Task.Run(async () =>
{
clientReady.TrySetResult(true);
try
{
foreach (var chan in (await Client.GetDMChannelsAsync()))
{
await chan.CloseAsync().ConfigureAwait(false);
}
}
catch
{
// ignored
}
finally
{
}
});
return Task.CompletedTask;
}
//connect
_log.Info("Shard {0} logging in ...", ShardId);
await Client.LoginAsync(TokenType.Bot, token).ConfigureAwait(false);
await Client.StartAsync().ConfigureAwait(false);
_log.Info("Waiting for all shards to connect...");
while (!Client.Shards.All(x => x.ConnectionState == ConnectionState.Connected))
{
_log.Info("Connecting... {0}/{1}", Client.Shards.Count(x => x.ConnectionState == ConnectionState.Connected), Client.Shards.Count);
await Task.Delay(1000).ConfigureAwait(false);
Client.Ready += SetClientReady;
await clientReady.Task.ConfigureAwait(false);
Client.Ready -= SetClientReady;
Client.JoinedGuild += Client_JoinedGuild;
Client.LeftGuild += Client_LeftGuild;
_log.Info("Shard {0} logged in.", ShardId);
}
private Task Client_LeftGuild(SocketGuild arg)
{
_log.Info("Left server: {0} [{1}]", arg?.Name, arg?.Id);
return Task.CompletedTask;
}
private Task Client_JoinedGuild(SocketGuild arg)
{
_log.Info("Joined server: {0} [{1}]", arg?.Name, arg?.Id);
return Task.CompletedTask;
}
public async Task RunAsync(params string[] args)
{
if(ShardId == 0)
_log.Info("Starting NadekoBot v" + StatsService.BotVersion);
var sw = Stopwatch.StartNew();
await LoginAsync(Credentials.Token).ConfigureAwait(false);
_log.Info("Loading services...");
_log.Info($"Shard {ShardId} loading services...");
AddServices();
sw.Stop();
_log.Info($"Connected in {sw.Elapsed.TotalSeconds:F2} s");
_log.Info($"Shard {ShardId} connected in {sw.Elapsed.TotalSeconds:F2}s");
var stats = Services.GetService<IStatsService>();
stats.Initialize();
@ -282,7 +360,11 @@ namespace NadekoBot
var _ = await CommandService.AddModulesAsync(this.GetType().GetTypeInfo().Assembly);
//Console.WriteLine(string.Join(", ", CommandService.Commands
bool isPublicNadeko = false;
#if GLOBAL_NADEKO
isPublicNadeko = true;
#endif
//_log.Info(string.Join(", ", CommandService.Commands
// .Distinct(x => x.Name + x.Module.Name)
// .SelectMany(x => x.Aliases)
// .GroupBy(x => x)
@ -290,15 +372,17 @@ namespace NadekoBot
// .Select(x => x.Key + $"({x.Count()})")));
//unload modules which are not available on the public bot
#if GLOBAL_NADEKO
if(isPublicNadeko)
CommandService
.Modules
.ToArray()
.Where(x => x.Preconditions.Any(y => y.GetType() == typeof(NoPublicBot)))
.ForEach(x => CommandService.RemoveModuleAsync(x));
#endif
Ready = true;
_log.Info(await stats.Print().ConfigureAwait(false));
_log.Info($"Shard {ShardId} ready.");
//_log.Info(await stats.Print().ConfigureAwait(false));
}
private Task Client_Log(LogMessage arg)
@ -313,8 +397,14 @@ namespace NadekoBot
public async Task RunAndBlockAsync(params string[] args)
{
await RunAsync(args).ConfigureAwait(false);
StartSendingData();
if (ShardCoord != null)
await ShardCoord.RunAndBlockAsync();
else
{
await Task.Delay(-1).ConfigureAwait(false);
}
}
private void TerribleElevatedPermissionCheck()
{
@ -331,18 +421,29 @@ namespace NadekoBot
}
}
private static void SetupLogger()
private void SetupShard(int shardId, int parentProcessId, int port)
{
var logConfig = new LoggingConfiguration();
var consoleTarget = new ColoredConsoleTarget()
if (shardId != 0)
{
Layout = @"${date:format=HH\:mm\:ss} ${logger} | ${message}"
};
logConfig.AddTarget("Console", consoleTarget);
logConfig.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget));
LogManager.Configuration = logConfig;
new Thread(new ThreadStart(() =>
{
try
{
var p = Process.GetProcessById(parentProcessId);
if (p == null)
return;
p.WaitForExit();
}
finally
{
Environment.Exit(10);
}
})).Start();
}
else
{
ShardCoord = new ShardsCoordinator(port);
}
}
}
}

View File

@ -55,7 +55,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.9.9" />
<PackageReference Include="Discord.Net" Version="1.0.0-rc3-00746" />
<PackageReference Include="Discord.Net" Version="1.0.1-build-00785" />
<PackageReference Include="libvideo" Version="1.0.1" />
<PackageReference Include="CoreCLR-NCalc" Version="2.1.2" />
<PackageReference Include="Google.Apis.Urlshortener.v1" Version="1.19.0.138" />
@ -74,6 +74,7 @@
<PackageReference Include="Microsoft.Extensions.PlatformAbstractions" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
<PackageReference Include="NLog" Version="5.0.0-beta03" />
<PackageReference Include="NYoutubeDL" Version="0.4.4" />
<PackageReference Include="System.ValueTuple" Version="4.4.0-preview1-25305-02" />
<PackageReference Include="System.Xml.XPath" Version="4.3.0" />
</ItemGroup>
@ -90,5 +91,6 @@
<ItemGroup>
<Folder Include="Modules\Music\Classes\" />
<Folder Include="Utility\Services\" />
</ItemGroup>
</Project>

View File

@ -2,7 +2,17 @@
{
public class Program
{
public static void Main(string[] args) =>
new NadekoBot().RunAndBlockAsync(args).GetAwaiter().GetResult();
public static void Main(string[] args)
{
if (args.Length == 3 && int.TryParse(args[0], out int shardId) && int.TryParse(args[1], out int parentProcessId))
{
int? port = null;
if (int.TryParse(args[2], out var outPort))
port = outPort;
new NadekoBot(shardId, parentProcessId, outPort).RunAndBlockAsync(args).GetAwaiter().GetResult();
}
else
new NadekoBot(0, 0).RunAndBlockAsync(args).GetAwaiter().GetResult();
}
}
}

View File

@ -0,0 +1,7 @@
{
"profiles": {
"NadekoBot": {
"commandName": "Project"
}
}
}

View File

@ -814,7 +814,7 @@
<value>serverinfo sinfo</value>
</data>
<data name="serverinfo_desc" xml:space="preserve">
<value>Shows info about the server the bot is on. If no channel is supplied, it defaults to current one.</value>
<value>Shows info about the server the bot is on. If no server is supplied, it defaults to current one.</value>
</data>
<data name="serverinfo_usage" xml:space="preserve">
<value>`{0}sinfo Some Server`</value>
@ -1198,7 +1198,7 @@
<value>`{0}drawnew` or `{0}drawnew 5`</value>
</data>
<data name="shuffleplaylist_cmd" xml:space="preserve">
<value>playlistshuffle plsh</value>
<value>shuffle sh plsh</value>
</data>
<data name="shuffleplaylist_desc" xml:space="preserve">
<value>Shuffles the current playlist.</value>
@ -1377,15 +1377,6 @@
<data name="typeadd_usage" xml:space="preserve">
<value>`{0}typeadd wordswords`</value>
</data>
<data name="poll_cmd" xml:space="preserve">
<value>poll</value>
</data>
<data name="poll_desc" xml:space="preserve">
<value>Creates a poll which requires users to send the number of the voting option to the bot.</value>
</data>
<data name="poll_usage" xml:space="preserve">
<value>`{0}poll Question?;Answer1;Answ 2;A_3`</value>
</data>
<data name="pollend_cmd" xml:space="preserve">
<value>pollend</value>
</data>
@ -1476,6 +1467,15 @@
<data name="next_usage" xml:space="preserve">
<value>`{0}n` or `{0}n 5`</value>
</data>
<data name="play_cmd" xml:space="preserve">
<value>play start</value>
</data>
<data name="play_desc" xml:space="preserve">
<value>If no arguments are specified, acts as `{0}next 1` command. If you specify a song number, it will jump to that song. If you specify a search query, acts as a `{0}q` command</value>
</data>
<data name="play_usage" xml:space="preserve">
<value>`{0}play` or `{0}play 5` or `{0}play Dream Of Venice`</value>
</data>
<data name="stop_cmd" xml:space="preserve">
<value>stop s</value>
</data>
@ -1534,7 +1534,7 @@
<value>listqueue lq</value>
</data>
<data name="listqueue_desc" xml:space="preserve">
<value>Lists 15 currently queued songs per page. Default page is 1.</value>
<value>Lists 10 currently queued songs per page. Default page is 1.</value>
</data>
<data name="listqueue_usage" xml:space="preserve">
<value>`{0}lq` or `{0}lq 2`</value>
@ -2583,13 +2583,13 @@
<data name="togethertube_usage" xml:space="preserve">
<value>`{0}totube`</value>
</data>
<data name="publicpoll_cmd" xml:space="preserve">
<value>publicpoll ppoll</value>
<data name="poll_cmd" xml:space="preserve">
<value>poll ppoll</value>
</data>
<data name="publicpoll_desc" xml:space="preserve">
<data name="poll_desc" xml:space="preserve">
<value>Creates a public poll which requires users to type a number of the voting option in the channel command is ran in.</value>
</data>
<data name="publicpoll_usage" xml:space="preserve">
<data name="poll_usage" xml:space="preserve">
<value>`{0}ppoll Question?;Answer1;Answ 2;A_3`</value>
</data>
<data name="autotranslang_cmd" xml:space="preserve">
@ -2790,6 +2790,15 @@
<data name="hangman_usage" xml:space="preserve">
<value>`{0}hangman` or `{0}hangman movies`</value>
</data>
<data name="hangmanstop_cmd" xml:space="preserve">
<value>hangmanstop</value>
</data>
<data name="hangmanstop_desc" xml:space="preserve">
<value>Stops the active hangman game on this channel if it exists.</value>
</data>
<data name="hangmanstop_usage" xml:space="preserve">
<value>`{0}hangmanstop`</value>
</data>
<data name="crstatsclear_cmd" xml:space="preserve">
<value>crstatsclear</value>
</data>
@ -3096,14 +3105,14 @@
<data name="shardstats_usage" xml:space="preserve">
<value>`{0}shardstats` or `{0}shardstats 2`</value>
</data>
<data name="connectshard_cmd" xml:space="preserve">
<value>connectshard</value>
<data name="restartshard_cmd" xml:space="preserve">
<value>restartshard</value>
</data>
<data name="connectshard_desc" xml:space="preserve">
<data name="restartshard_desc" xml:space="preserve">
<value>Try (re)connecting a shard with a certain shardid when it dies. No one knows will it work. Keep an eye on the console for errors.</value>
</data>
<data name="connectshard_usage" xml:space="preserve">
<value>`{0}connectshard 2`</value>
<data name="restartshard_usage" xml:space="preserve">
<value>`{0}restartshard 2`</value>
</data>
<data name="shardid_cmd" xml:space="preserve">
<value>shardid</value>
@ -3258,6 +3267,15 @@
<data name="warnlog_usage" xml:space="preserve">
<value>`{0}warnlog @b1nzy`</value>
</data>
<data name="warnlogall_cmd" xml:space="preserve">
<value>warnlogall</value>
</data>
<data name="warnlogall_desc" xml:space="preserve">
<value>See a list of all warnings on the server. 15 users per page.</value>
</data>
<data name="warnlogall_usage" xml:space="preserve">
<value>`{0}warnlogall` or `{0}warnlogall 2`</value>
</data>
<data name="warn_cmd" xml:space="preserve">
<value>warn</value>
</data>

View File

@ -12,12 +12,12 @@ namespace NadekoBot.Services.Administration
public class AutoAssignRoleService
{
private readonly Logger _log;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
//guildid/roleid
public ConcurrentDictionary<ulong, ulong> AutoAssignedRoles { get; }
public AutoAssignRoleService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs)
public AutoAssignRoleService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;

View File

@ -16,9 +16,9 @@ namespace NadekoBot.Services.Administration
private readonly Logger _log;
private readonly DbService _db;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public GameVoiceChannelService(DiscordShardedClient client, DbService db, IEnumerable<GuildConfig> gcs)
public GameVoiceChannelService(DiscordSocketClient client, DbService db, IEnumerable<GuildConfig> gcs)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;

View File

@ -5,15 +5,18 @@ using System.Collections.Generic;
using System.Linq;
using System.Collections.Concurrent;
using NadekoBot.Services;
using Discord.WebSocket;
namespace NadekoBot.Services.Administration
{
public class GuildTimezoneService
{
//hack >.>
public static ConcurrentDictionary<ulong, GuildTimezoneService> AllServices { get; } = new ConcurrentDictionary<ulong, GuildTimezoneService>();
private ConcurrentDictionary<ulong, TimeZoneInfo> _timezones;
private readonly DbService _db;
public GuildTimezoneService(IEnumerable<GuildConfig> gcs, DbService db)
public GuildTimezoneService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, DbService db)
{
_timezones = gcs
.Select(x =>
@ -21,6 +24,9 @@ namespace NadekoBot.Services.Administration
TimeZoneInfo tz;
try
{
if (x.TimeZoneId == null)
tz = null;
else
tz = TimeZoneInfo.FindSystemTimeZoneById(x.TimeZoneId);
}
catch
@ -33,6 +39,9 @@ namespace NadekoBot.Services.Administration
.ToDictionary(x => x.Item1, x => x.Item2)
.ToConcurrent();
var curUser = client.CurrentUser;
if (curUser != null)
AllServices.TryAdd(curUser.Id, this);
_db = db;
}

View File

@ -16,11 +16,24 @@ namespace NadekoBot.Services.Administration
public class LogCommandService
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly Logger _log;
private string PrettyCurrentTime => $"【{DateTime.UtcNow:HH:mm:ss}】";
private string CurrentTime => $"{DateTime.UtcNow:HH:mm:ss}";
private string PrettyCurrentTime(IGuild g)
{
var time = DateTime.UtcNow;
if(g != null)
time = TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(g.Id));
return $"【{time:HH:mm:ss}】";
}
private string CurrentTime(IGuild g)
{
DateTime time = DateTime.UtcNow;
if (g != null)
time = TimeZoneInfo.ConvertTime(time, _tz.GetTimeZoneOrUtc(g.Id));
return $"{time:HH:mm:ss}";
}
public ConcurrentDictionary<ulong, LogSetting> GuildLogSettings { get; }
@ -30,9 +43,10 @@ namespace NadekoBot.Services.Administration
private readonly DbService _db;
private readonly MuteService _mute;
private readonly ProtectionService _prot;
private readonly GuildTimezoneService _tz;
public LogCommandService(DiscordShardedClient client, NadekoStrings strings,
IEnumerable<GuildConfig> gcs, DbService db, MuteService mute, ProtectionService prot)
public LogCommandService(DiscordSocketClient client, NadekoStrings strings,
IEnumerable<GuildConfig> gcs, DbService db, MuteService mute, ProtectionService prot, GuildTimezoneService tz)
{
_client = client;
_log = LogManager.GetCurrentClassLogger();
@ -40,6 +54,7 @@ namespace NadekoBot.Services.Administration
_db = db;
_mute = mute;
_prot = prot;
_tz = tz;
GuildLogSettings = gcs
.ToDictionary(g => g.GuildId, g => g.LogSetting)
@ -51,14 +66,15 @@ namespace NadekoBot.Services.Administration
{
var keys = PresenceUpdates.Keys.ToList();
await Task.WhenAll(keys.Select(async key =>
await Task.WhenAll(keys.Select(key =>
{
if (PresenceUpdates.TryRemove(key, out List<string> messages))
try { await key.SendConfirmAsync(GetText(key.Guild, "presence_updates"), string.Join(Environment.NewLine, messages)); }
catch
if (PresenceUpdates.TryRemove(key, out var msgs))
{
// ignored
var title = GetText(key.Guild, "presence_updates");
var desc = string.Join(Environment.NewLine, msgs);
return key.SendConfirmAsync(title, desc.TrimTo(2048));
}
return Task.CompletedTask;
}));
}
catch (Exception ex)
@ -74,7 +90,7 @@ namespace NadekoBot.Services.Administration
_client.UserUnbanned += _client_UserUnbanned;
_client.UserJoined += _client_UserJoined;
_client.UserLeft += _client_UserLeft;
_client.UserPresenceUpdated += _client_UserPresenceUpdated;
//_client.UserPresenceUpdated += _client_UserPresenceUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated;
_client.UserVoiceStateUpdated += _client_UserVoiceStateUpdated_TTS;
_client.GuildMemberUpdated += _client_GuildUserUpdated;
@ -124,17 +140,20 @@ namespace NadekoBot.Services.Administration
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.AddField(fb => fb.WithName("Old Name").WithValue($"{before.Username}").WithIsInline(true))
.AddField(fb => fb.WithName("New Name").WithValue($"{after.Username}").WithIsInline(true))
.WithFooter(fb => fb.WithText(CurrentTime))
.WithFooter(fb => fb.WithText(CurrentTime(g)))
.WithOkColor();
}
else if (before.AvatarId != after.AvatarId)
{
embed.WithTitle("👥" + GetText(g, "avatar_changed"))
.WithDescription($"{before.Username}#{before.Discriminator} | {before.Id}")
.WithThumbnailUrl(before.GetAvatarUrl())
.WithImageUrl(after.GetAvatarUrl())
.WithFooter(fb => fb.WithText(CurrentTime))
.WithFooter(fb => fb.WithText(CurrentTime(g)))
.WithOkColor();
if (Uri.IsWellFormedUriString(before.GetAvatarUrl(), UriKind.Absolute))
embed.WithThumbnailUrl(before.GetAvatarUrl());
if (Uri.IsWellFormedUriString(after.GetAvatarUrl(), UriKind.Absolute))
embed.WithImageUrl(after.GetAvatarUrl());
}
else
{
@ -244,7 +263,7 @@ namespace NadekoBot.Services.Administration
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName(mutes))
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(fb => fb.WithText(CurrentTime))
.WithFooter(fb => fb.WithText(CurrentTime(usr.Guild)))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
@ -287,7 +306,7 @@ namespace NadekoBot.Services.Administration
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName(mutes))
.WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
.WithFooter(fb => fb.WithText($"{CurrentTime}"))
.WithFooter(fb => fb.WithText($"{CurrentTime(usr.Guild)}"))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
@ -335,7 +354,7 @@ namespace NadekoBot.Services.Administration
var embed = new EmbedBuilder().WithAuthor(eab => eab.WithName($"🛡 Anti-{protection}"))
.WithTitle(GetText(logChannel.Guild, "users") + " " + punishment)
.WithDescription(string.Join("\n", users.Select(u => u.ToString())))
.WithFooter(fb => fb.WithText($"{CurrentTime}"))
.WithFooter(fb => fb.WithText(CurrentTime(logChannel.Guild)))
.WithOkColor();
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
@ -354,21 +373,21 @@ namespace NadekoBot.Services.Administration
{
try
{
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting)
|| (logSetting.UserUpdatedId == null))
if (!GuildLogSettings.TryGetValue(before.Guild.Id, out LogSetting logSetting))
return;
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) == null)
return;
var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(CurrentTime))
if (logSetting.UserUpdatedId != null && (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) != null)
{
var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(CurrentTime(before.Guild)))
.WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
if (before.Nickname != after.Nickname)
{
embed.WithAuthor(eab => eab.WithName("👥 " + GetText(logChannel.Guild, "nick_change")))
.AddField(efb => efb.WithName(GetText(logChannel.Guild, "old_nick")).WithValue($"{before.Nickname}#{before.Discriminator}"))
.AddField(efb => efb.WithName(GetText(logChannel.Guild, "new_nick")).WithValue($"{after.Nickname}#{after.Discriminator}"));
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
else if (!before.Roles.SequenceEqual(after.Roles))
{
@ -384,11 +403,30 @@ namespace NadekoBot.Services.Administration
embed.WithAuthor(eab => eab.WithName("⚔ " + GetText(logChannel.Guild, "user_role_rem")))
.WithDescription(string.Join(", ", diffRoles).SanitizeMentions());
}
}
else
return;
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
}
logChannel = null;
if (logSetting.LogUserPresenceId != null && (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserPresence)) != null)
{
if (before.Status != after.Status)
{
var str = "🎭" + Format.Code(PrettyCurrentTime(after.Guild)) +
GetText(logChannel.Guild, "user_status_change",
"👤" + Format.Bold(after.Username),
Format.Bold(after.Status.ToString()));
PresenceUpdates.AddOrUpdate(logChannel,
new List<string>() { str }, (id, list) => { list.Add(str); return list; });
}
else if (before.Game?.Name != after.Game?.Name)
{
var str = $"👾`{PrettyCurrentTime(after.Guild)}`👤__**{after.Username}**__ is now playing **{after.Game?.Name ?? "-"}**.";
PresenceUpdates.AddOrUpdate(logChannel,
new List<string>() { str }, (id, list) => { list.Add(str); return list; });
}
}
}
catch
{
// ignored
@ -417,7 +455,7 @@ namespace NadekoBot.Services.Administration
if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.ChannelUpdated)) == null)
return;
var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(CurrentTime));
var embed = new EmbedBuilder().WithOkColor().WithFooter(efb => efb.WithText(CurrentTime(before.Guild)));
var beforeTextChannel = cbefore as ITextChannel;
var afterTextChannel = cafter as ITextChannel;
@ -477,7 +515,7 @@ namespace NadekoBot.Services.Administration
.WithOkColor()
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false);
.WithFooter(efb => efb.WithText(CurrentTime(ch.Guild)))).ConfigureAwait(false);
}
catch
{
@ -515,7 +553,7 @@ namespace NadekoBot.Services.Administration
.WithOkColor()
.WithTitle("🆕 " + title)
.WithDescription($"{ch.Name} | {ch.Id}")
.WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false);
.WithFooter(efb => efb.WithText(CurrentTime(ch.Guild)))).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
});
@ -549,23 +587,23 @@ namespace NadekoBot.Services.Administration
string str = null;
if (beforeVch?.Guild == afterVch?.Guild)
{
str = "🎙" + Format.Code(PrettyCurrentTime) + GetText(logChannel.Guild, "user_vmoved",
str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, "user_vmoved",
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(beforeVch?.Name ?? ""), Format.Bold(afterVch?.Name ?? ""));
}
else if (beforeVch == null)
{
str = "🎙" + Format.Code(PrettyCurrentTime) + GetText(logChannel.Guild, "user_vjoined",
str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, "user_vjoined",
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(afterVch.Name ?? ""));
}
else if (afterVch == null)
{
str = "🎙" + Format.Code(PrettyCurrentTime) + GetText(logChannel.Guild, "user_vleft",
str = "🎙" + Format.Code(PrettyCurrentTime(usr.Guild)) + GetText(logChannel.Guild, "user_vleft",
"👤" + Format.Bold(usr.Username + "#" + usr.Discriminator),
Format.Bold(beforeVch.Name ?? ""));
}
if (str != null)
if (!string.IsNullOrWhiteSpace(str))
PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
}
catch
@ -576,48 +614,48 @@ namespace NadekoBot.Services.Administration
return Task.CompletedTask;
}
private Task _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after)
{
var _ = Task.Run(async () =>
{
try
{
var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild;
if (guild == null)
return;
if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
|| (logSetting.LogUserPresenceId == null)
|| before.Status == after.Status)
return;
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null)
return;
string str = "";
if (before.Status != after.Status)
str = "🎭" + Format.Code(PrettyCurrentTime) +
GetText(logChannel.Guild, "user_status_change",
"👤" + Format.Bold(usr.Username),
Format.Bold(after.Status.ToString()));
//if (before.Game?.Name != after.Game?.Name)
//private Task _client_UserPresenceUpdated(Optional<SocketGuild> optGuild, SocketUser usr, SocketPresence before, SocketPresence after)
//{
// if (str != "")
// str += "\n";
// str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**.";
//}
// var _ = Task.Run(async () =>
// {
// try
// {
// var guild = optGuild.GetValueOrDefault() ?? (usr as SocketGuildUser)?.Guild;
PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
}
catch
{
// ignored
}
});
return Task.CompletedTask;
}
// if (guild == null)
// return;
// if (!GuildLogSettings.TryGetValue(guild.Id, out LogSetting logSetting)
// || (logSetting.LogUserPresenceId == null)
// || before.Status == after.Status)
// return;
// ITextChannel logChannel;
// if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserPresence)) == null)
// return;
// string str = "";
// if (before.Status != after.Status)
// str = "🎭" + Format.Code(PrettyCurrentTime(g)) +
// GetText(logChannel.Guild, "user_status_change",
// "👤" + Format.Bold(usr.Username),
// Format.Bold(after.Status.ToString()));
// //if (before.Game?.Name != after.Game?.Name)
// //{
// // if (str != "")
// // str += "\n";
// // str += $"👾`{prettyCurrentTime}`👤__**{usr.Username}**__ is now playing **{after.Game?.Name}**.";
// //}
// PresenceUpdates.AddOrUpdate(logChannel, new List<string>() { str }, (id, list) => { list.Add(str); return list; });
// }
// catch
// {
// // ignored
// }
// });
// return Task.CompletedTask;
//}
private Task _client_UserLeft(IGuildUser usr)
{
@ -632,14 +670,17 @@ namespace NadekoBot.Services.Administration
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserLeft)) == null)
return;
await logChannel.EmbedAsync(new EmbedBuilder()
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("❌ " + GetText(logChannel.Guild, "user_left"))
.WithThumbnailUrl(usr.GetAvatarUrl())
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false);
.WithFooter(efb => efb.WithText(CurrentTime(usr.Guild)));
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
embed.WithThumbnailUrl(usr.GetAvatarUrl());
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch
{
@ -663,13 +704,17 @@ namespace NadekoBot.Services.Administration
if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserJoined)) == null)
return;
await logChannel.EmbedAsync(new EmbedBuilder()
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("✅ " + GetText(logChannel.Guild, "user_joined"))
.WithThumbnailUrl(usr.GetAvatarUrl())
.WithDescription($"{usr}")
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false);
.WithFooter(efb => efb.WithText(CurrentTime(usr.Guild)));
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
embed.WithThumbnailUrl(usr.GetAvatarUrl());
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
});
@ -689,14 +734,17 @@ namespace NadekoBot.Services.Administration
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) == null)
return;
await logChannel.EmbedAsync(new EmbedBuilder()
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("♻️ " + GetText(logChannel.Guild, "user_unbanned"))
.WithThumbnailUrl(usr.GetAvatarUrl())
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false);
.WithFooter(efb => efb.WithText(CurrentTime(guild)));
if (Uri.IsWellFormedUriString(usr.GetAvatarUrl(), UriKind.Absolute))
embed.WithThumbnailUrl(usr.GetAvatarUrl());
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
});
@ -716,13 +764,19 @@ namespace NadekoBot.Services.Administration
ITextChannel logChannel;
if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserBanned)) == null)
return;
await logChannel.EmbedAsync(new EmbedBuilder()
var embed = new EmbedBuilder()
.WithOkColor()
.WithTitle("🚫 " + GetText(logChannel.Guild, "user_banned"))
.WithThumbnailUrl(usr.GetAvatarUrl())
.WithDescription(usr.ToString())
.AddField(efb => efb.WithName("Id").WithValue(usr.Id.ToString()))
.WithFooter(efb => efb.WithText(CurrentTime))).ConfigureAwait(false);
.WithFooter(efb => efb.WithText(CurrentTime(guild)));
var avatarUrl = usr.GetAvatarUrl();
if (Uri.IsWellFormedUriString(avatarUrl, UriKind.Absolute))
embed.WithThumbnailUrl(usr.GetAvatarUrl());
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}
catch (Exception ex) { _log.Warn(ex); }
});
@ -757,7 +811,7 @@ namespace NadekoBot.Services.Administration
.WithDescription(msg.Author.ToString())
.AddField(efb => efb.WithName(GetText(logChannel.Guild, "content")).WithValue(string.IsNullOrWhiteSpace(msg.Content) ? "-" : msg.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(msg.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(CurrentTime));
.WithFooter(efb => efb.WithText(CurrentTime(channel.Guild)));
if (msg.Attachments.Any())
embed.AddField(efb => efb.WithName(GetText(logChannel.Guild, "attachments")).WithValue(string.Join(", ", msg.Attachments.Select(a => a.Url))).WithIsInline(false));
@ -809,7 +863,7 @@ namespace NadekoBot.Services.Administration
.AddField(efb => efb.WithName(GetText(logChannel.Guild, "old_msg")).WithValue(string.IsNullOrWhiteSpace(before.Content) ? "-" : before.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName(GetText(logChannel.Guild, "new_msg")).WithValue(string.IsNullOrWhiteSpace(after.Content) ? "-" : after.Resolve(userHandling: TagHandling.FullName)).WithIsInline(false))
.AddField(efb => efb.WithName("Id").WithValue(after.Id.ToString()).WithIsInline(false))
.WithFooter(efb => efb.WithText(CurrentTime));
.WithFooter(efb => efb.WithText(CurrentTime(channel.Guild)));
await logChannel.EmbedAsync(embed).ConfigureAwait(false);
}

View File

@ -33,10 +33,10 @@ namespace NadekoBot.Services.Administration
private static readonly OverwritePermissions denyOverwrite = new OverwritePermissions(sendMessages: PermValue.Deny, attachFiles: PermValue.Deny);
private readonly Logger _log = LogManager.GetCurrentClassLogger();
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly DbService _db;
public MuteService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, DbService db)
public MuteService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, DbService db)
{
_client = client;
_db = db;

View File

@ -1,102 +1,75 @@
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.DataStructures.Replacements;
using NadekoBot.Services.Database.Models;
using NadekoBot.Services.Music;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace NadekoBot.Services.Administration
{
//todo 99 - Could make a placeholder service, which can work for any module
//and have replacements which are dependent on the types provided in the constructor
public class PlayingRotateService
{
public List<PlayingStatus> RotatingStatusMessages { get; }
public volatile bool RotatingStatuses;
private readonly Timer _t;
private readonly DiscordShardedClient _client;
private readonly BotConfig _bc;
private readonly DiscordSocketClient _client;
private readonly MusicService _music;
private readonly Logger _log;
private readonly Replacer _rep;
private readonly DbService _db;
public BotConfig BotConfig { get; private set; } //todo load whole botconifg, not just for this service when you have the time
private class TimerState
{
public int Index { get; set; }
}
public PlayingRotateService(DiscordShardedClient client, BotConfig bc, MusicService music)
public PlayingRotateService(DiscordSocketClient client, BotConfig bc, MusicService music, DbService db)
{
_client = client;
_bc = bc;
BotConfig = bc;
_music = music;
_db = db;
_log = LogManager.GetCurrentClassLogger();
RotatingStatusMessages = _bc.RotatingStatusMessages;
RotatingStatuses = _bc.RotatingStatuses;
_rep = new ReplacementBuilder()
.WithClient(client)
.WithStats(client)
.WithMusic(music)
.Build();
_t = new Timer(async (objState) =>
{
try
{
using (var uow = _db.UnitOfWork)
{
BotConfig = uow.BotConfig.GetOrCreate();
}
var state = (TimerState)objState;
if (!RotatingStatuses)
if (!BotConfig.RotatingStatuses)
return;
if (state.Index >= RotatingStatusMessages.Count)
if (state.Index >= BotConfig.RotatingStatusMessages.Count)
state.Index = 0;
if (!RotatingStatusMessages.Any())
if (!BotConfig.RotatingStatusMessages.Any())
return;
var status = RotatingStatusMessages[state.Index++].Status;
var status = BotConfig.RotatingStatusMessages[state.Index++].Status;
if (string.IsNullOrWhiteSpace(status))
return;
PlayingPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(_client,_music)));
var shards = _client.Shards;
for (int i = 0; i < shards.Count; i++)
{
var curShard = shards.ElementAt(i);
ShardSpecificPlaceholders.ForEach(e => status = status.Replace(e.Key, e.Value(curShard)));
try { await shards.ElementAt(i).SetGameAsync(status).ConfigureAwait(false); }
status = _rep.Replace(status);
try { await client.SetGameAsync(status).ConfigureAwait(false); }
catch (Exception ex)
{
_log.Warn(ex);
}
}
}
catch (Exception ex)
{
_log.Warn("Rotating playing status errored.\n" + ex);
}
}, new TimerState(), TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
public Dictionary<string, Func<DiscordShardedClient, MusicService, string>> PlayingPlaceholders { get; } =
new Dictionary<string, Func<DiscordShardedClient, MusicService, string>> {
{ "%servers%", (c, ms) => c.Guilds.Count.ToString()},
{ "%users%", (c, ms) => c.Guilds.Sum(s => s.Users.Count).ToString()},
{ "%playing%", (c, ms) => {
var cnt = ms.MusicPlayers.Count(kvp => kvp.Value.CurrentSong != null);
if (cnt != 1) return cnt.ToString();
try {
var mp = ms.MusicPlayers.FirstOrDefault();
return mp.Value.CurrentSong.SongInfo.Title;
}
catch {
return "No songs";
}
}
},
{ "%queued%", (c, ms) => ms.MusicPlayers.Sum(kvp => kvp.Value.Playlist.Count).ToString()},
{ "%time%", (c, ms) => DateTime.Now.ToString("HH:mm " + TimeZoneInfo.Local.StandardName.GetInitials()) },
{ "%shardcount%", (c, ms) => c.Shards.Count.ToString() },
};
public Dictionary<string, Func<DiscordSocketClient, string>> ShardSpecificPlaceholders { get; } =
new Dictionary<string, Func<DiscordSocketClient, string>> {
{ "%shardid%", (client) => client.ShardId.ToString()},
{ "%shardguilds%", (client) => client.Guilds.Count.ToString()},
};
}
}

View File

@ -21,10 +21,10 @@ namespace NadekoBot.Services.Administration
public event Func<PunishmentAction, ProtectionType, IGuildUser[], Task> OnAntiProtectionTriggered = delegate { return Task.CompletedTask; };
private readonly Logger _log;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly MuteService _mute;
public ProtectionService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, MuteService mute)
public ProtectionService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, MuteService mute)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;

View File

@ -12,7 +12,7 @@ namespace NadekoBot.Services.Administration
public class PruneService
{
//channelids where prunes are currently occuring
private ConcurrentHashSet<ulong> _pruningChannels = new ConcurrentHashSet<ulong>();
private ConcurrentHashSet<ulong> _pruningGuilds = new ConcurrentHashSet<ulong>();
private readonly TimeSpan twoWeeks = TimeSpan.FromDays(14);
public async Task PruneWhere(ITextChannel channel, int amount, Func<IMessage, bool> predicate)
@ -21,14 +21,14 @@ namespace NadekoBot.Services.Administration
if (amount <= 0)
throw new ArgumentOutOfRangeException(nameof(amount));
if (!_pruningChannels.Add(channel.Id))
if (!_pruningGuilds.Add(channel.GuildId))
return;
try
{
IMessage[] msgs;
IMessage lastMessage = null;
msgs = (await channel.GetMessagesAsync().Flatten()).Where(predicate).Take(amount).ToArray();
msgs = (await channel.GetMessagesAsync(50).Flatten()).Where(predicate).Take(amount).ToArray();
while (amount > 0 && msgs.Any())
{
lastMessage = msgs[msgs.Length - 1];
@ -52,9 +52,9 @@ namespace NadekoBot.Services.Administration
//this isn't good, because this still work as if i want to remove only specific user's messages from the last
//100 messages, Maybe this needs to be reduced by msgs.Length instead of 100
amount -= 100;
amount -= 50;
if(amount > 0)
msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before).Flatten()).Where(predicate).Take(amount).ToArray();
msgs = (await channel.GetMessagesAsync(lastMessage, Direction.Before, 50).Flatten()).Where(predicate).Take(amount).ToArray();
}
}
catch
@ -63,7 +63,7 @@ namespace NadekoBot.Services.Administration
}
finally
{
_pruningChannels.TryRemove(channel.Id);
_pruningGuilds.TryRemove(channel.GuildId);
}
}
}

View File

@ -19,9 +19,9 @@ namespace NadekoBot.Services.Administration
public ConcurrentDictionary<ulong, HashSet<ulong>> IgnoredUsers = new ConcurrentDictionary<ulong, HashSet<ulong>>();
private readonly Logger _log;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public SlowmodeService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs)
public SlowmodeService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs)
{
_log = LogManager.GetCurrentClassLogger();
_client = client;

View File

@ -23,11 +23,11 @@ namespace NadekoBot.Services.Administration
private readonly Logger _log;
private readonly ILocalization _localization;
private readonly NadekoStrings _strings;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly IBotCredentials _creds;
private ImmutableArray<AsyncLazy<IDMChannel>> ownerChannels = new ImmutableArray<AsyncLazy<IDMChannel>>();
public SelfService(DiscordShardedClient client, NadekoBot bot, CommandHandler cmdHandler, DbService db,
public SelfService(DiscordSocketClient client, NadekoBot bot, CommandHandler cmdHandler, DbService db,
BotConfig bc, ILocalization localization, NadekoStrings strings, IBotCredentials creds)
{
_bot = bot;
@ -39,12 +39,8 @@ namespace NadekoBot.Services.Administration
_client = client;
_creds = creds;
using (var uow = _db.UnitOfWork)
{
var config = uow.BotConfig.GetOrCreate();
ForwardDMs = config.ForwardMessages;
ForwardDMsToAllOwners = config.ForwardToAllOwners;
}
ForwardDMs = bc.ForwardMessages;
ForwardDMsToAllOwners = bc.ForwardToAllOwners;
var _ = Task.Run(async () =>
{
@ -67,12 +63,8 @@ namespace NadekoBot.Services.Administration
_client.Guilds.SelectMany(g => g.Users);
if(client.ShardId == 0)
LoadOwnerChannels();
if (!ownerChannels.Any())
_log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
else
_log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels.");
});
}
@ -81,11 +73,9 @@ namespace NadekoBot.Services.Administration
var hs = new HashSet<ulong>(_creds.OwnerIds);
var channels = new Dictionary<ulong, AsyncLazy<IDMChannel>>();
foreach (var s in _client.Shards)
if (hs.Count > 0)
{
if (hs.Count == 0)
break;
foreach (var g in s.Guilds)
foreach (var g in _client.Guilds)
{
if (hs.Count == 0)
break;
@ -94,7 +84,7 @@ namespace NadekoBot.Services.Administration
{
if (hs.Remove(u.Id))
{
channels.Add(u.Id, new AsyncLazy<IDMChannel>(async () => await u.CreateDMChannelAsync()));
channels.Add(u.Id, new AsyncLazy<IDMChannel>(async () => await u.GetOrCreateDMChannelAsync()));
if (hs.Count == 0)
break;
}
@ -105,10 +95,15 @@ namespace NadekoBot.Services.Administration
ownerChannels = channels.OrderBy(x => _creds.OwnerIds.IndexOf(x.Key))
.Select(x => x.Value)
.ToImmutableArray();
if (!ownerChannels.Any())
_log.Warn("No owner channels created! Make sure you've specified correct OwnerId in the credentials.json file.");
else
_log.Info($"Created {ownerChannels.Length} out of {_creds.OwnerIds.Length} owner message channels.");
}
// forwards dms
public async Task LateExecute(DiscordShardedClient client, IGuild guild, IUserMessage msg)
public async Task LateExecute(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{
if (msg.Channel is IDMChannel && ForwardDMs && ownerChannels.Length > 0)
{

View File

@ -14,11 +14,11 @@ namespace NadekoBot.Services.Administration
{
private readonly Logger _log;
private readonly DbService _db;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public ConcurrentDictionary<ulong, ConcurrentDictionary<ulong, IRole>> VcRoles { get; }
public VcRoleService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, DbService db)
public VcRoleService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, DbService db)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;

View File

@ -20,12 +20,12 @@ namespace NadekoBot.Services.Administration
public readonly ConcurrentHashSet<ulong> VoicePlusTextCache;
private readonly ConcurrentDictionary<ulong, SemaphoreSlim> _guildLockObjects = new ConcurrentDictionary<ulong, SemaphoreSlim>();
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly NadekoStrings _strings;
private readonly DbService _db;
private readonly Logger _log;
public VplusTService(DiscordShardedClient client, IEnumerable<GuildConfig> gcs, NadekoStrings strings,
public VplusTService(DiscordSocketClient client, IEnumerable<GuildConfig> gcs, NadekoStrings strings,
DbService db)
{
_client = client;

View File

@ -1,6 +1,7 @@
using Discord;
using Discord.WebSocket;
using NadekoBot.Extensions;
using NadekoBot.Services.Database;
using NadekoBot.Services.Database.Models;
using System;
using System.Collections.Concurrent;
@ -17,7 +18,7 @@ namespace NadekoBot.Services.ClashOfClans
// shouldn't be here
public class ClashOfClansService
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly DbService _db;
private readonly ILocalization _localization;
private readonly NadekoStrings _strings;
@ -25,18 +26,18 @@ namespace NadekoBot.Services.ClashOfClans
public ConcurrentDictionary<ulong, List<ClashWar>> ClashWars { get; set; }
public ClashOfClansService(DiscordShardedClient client, DbService db, ILocalization localization, NadekoStrings strings)
public ClashOfClansService(DiscordSocketClient client, DbService db,
ILocalization localization, NadekoStrings strings, IUnitOfWork uow,
List<long> guilds)
{
_client = client;
_db = db;
_localization = localization;
_strings = strings;
using (var uow = _db.UnitOfWork)
{
ClashWars = new ConcurrentDictionary<ulong, List<ClashWar>>(
uow.ClashOfClans
.GetAllWars()
.GetAllWars(guilds)
.Select(cw =>
{
cw.Channel = _client.GetGuild(cw.GuildId)?
@ -46,7 +47,6 @@ namespace NadekoBot.Services.ClashOfClans
.Where(cw => cw.Channel != null)
.GroupBy(cw => cw.GuildId)
.ToDictionary(g => g.Key, g => g.ToList()));
}
checkWarTimer = new Timer(async _ =>
{

View File

@ -28,7 +28,7 @@ namespace NadekoBot.Services
{
public const int GlobalCommandsCooldown = 750;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly CommandService _commandService;
private readonly Logger _log;
private readonly IBotCredentials _creds;
@ -48,7 +48,7 @@ namespace NadekoBot.Services
public ConcurrentHashSet<ulong> UsersOnShortCooldown { get; } = new ConcurrentHashSet<ulong>();
private readonly Timer _clearUsersOnShortCooldown;
public CommandHandler(DiscordShardedClient client, DbService db, BotConfig bc, IEnumerable<GuildConfig> gcs, CommandService commandService, IBotCredentials credentials, NadekoBot bot)
public CommandHandler(DiscordSocketClient client, DbService db, BotConfig bc, IEnumerable<GuildConfig> gcs, CommandService commandService, IBotCredentials credentials, NadekoBot bot)
{
_client = client;
_commandService = commandService;
@ -355,7 +355,7 @@ namespace NadekoBot.Services
}
}
var execResult = await commands[i].ExecuteAsync(context, parseResult, serviceProvider);
var execResult = (ExecuteResult)(await commands[i].ExecuteAsync(context, parseResult, serviceProvider));
if (execResult.Exception != null && (!(execResult.Exception is HttpException he) || he.DiscordCode != 50013))
{
lock (errorLogLock)

View File

@ -10,6 +10,7 @@ using System;
using System.Threading.Tasks;
using NadekoBot.Services.Permissions;
using NadekoBot.Extensions;
using NadekoBot.Services.Database;
namespace NadekoBot.Services.CustomReactions
{
@ -22,13 +23,14 @@ namespace NadekoBot.Services.CustomReactions
private readonly Logger _log;
private readonly DbService _db;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly PermissionService _perms;
private readonly CommandHandler _cmd;
private readonly BotConfig _bc;
private readonly NadekoStrings _strings;
public CustomReactionsService(PermissionService perms, DbService db,
DiscordShardedClient client, CommandHandler cmd, BotConfig bc)
public CustomReactionsService(PermissionService perms, DbService db, NadekoStrings strings,
DiscordSocketClient client, CommandHandler cmd, BotConfig bc, IUnitOfWork uow)
{
_log = LogManager.GetCurrentClassLogger();
_db = db;
@ -36,17 +38,12 @@ namespace NadekoBot.Services.CustomReactions
_perms = perms;
_cmd = cmd;
_bc = bc;
_strings = strings;
var sw = Stopwatch.StartNew();
using (var uow = _db.UnitOfWork)
{
var items = uow.CustomReactions.GetAll();
GuildReactions = new ConcurrentDictionary<ulong, CustomReaction[]>(items.Where(g => g.GuildId != null && g.GuildId != 0).GroupBy(k => k.GuildId.Value).ToDictionary(g => g.Key, g => g.ToArray()));
GlobalReactions = items.Where(g => g.GuildId == null || g.GuildId == 0).ToArray();
}
sw.Stop();
_log.Debug($"Loaded in {sw.Elapsed.TotalSeconds:F2}s");
}
public void ClearStats() => ReactionStats.Clear();
@ -98,7 +95,7 @@ namespace NadekoBot.Services.CustomReactions
return greaction;
}
public async Task<bool> TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage msg)
public async Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage msg)
{
// maybe this message is a custom reaction
var cr = await Task.Run(() => TryGetCustomReaction(msg)).ConfigureAwait(false);
@ -114,7 +111,7 @@ namespace NadekoBot.Services.CustomReactions
{
if (pc.Verbose)
{
var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)}** is preventing this action.";
var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)));
try { await msg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { }
_log.Info(returnMsg);
}

View File

@ -3,6 +3,7 @@ using AngleSharp.Dom.Html;
using Discord;
using Discord.WebSocket;
using NadekoBot.DataStructures;
using NadekoBot.DataStructures.Replacements;
using NadekoBot.Extensions;
using NadekoBot.Services.Database.Models;
using System;
@ -15,62 +16,12 @@ namespace NadekoBot.Services.CustomReactions
{
public static class Extensions
{
public static Dictionary<string, Func<IUserMessage, string, string>> responsePlaceholders = new Dictionary<string, Func<IUserMessage, string, string>>()
{
{"%target%", (ctx, trigger) => { return ctx.Content.Substring(trigger.Length).Trim().SanitizeMentions(); } },
{"%rnduser%", (ctx, client) => {
//var ch = ctx.Channel as ITextChannel;
//if(ch == null)
// return "";
//var g = ch.Guild as SocketGuild;
//if(g == null)
// return "";
//try {
// var usr = g.Users.Skip(new NadekoRandom().Next(0, g.Users.Count)).FirstOrDefault();
// return usr.Mention;
//}
//catch {
return "[%rnduser% is temp. disabled]";
//}
//var users = g.Users.ToArray();
//return users[new NadekoRandom().Next(0, users.Length-1)].Mention;
} },
};
public static Dictionary<string, Func<IUserMessage, DiscordShardedClient, string>> placeholders = new Dictionary<string, Func<IUserMessage, DiscordShardedClient, string>>()
{
{"%mention%", (ctx, client) => { return $"<@{client.CurrentUser.Id}>"; } },
{"%user%", (ctx, client) => { return ctx.Author.Mention; } },
//{"%rng%", (ctx) => { return new NadekoRandom().Next(0,10).ToString(); } }
};
private static readonly Regex rngRegex = new Regex("%rng(?:(?<from>(?:-)?\\d+)-(?<to>(?:-)?\\d+))?%", RegexOptions.Compiled);
private static readonly Regex imgRegex = new Regex("%(img|image):(?<tag>.*?)%", RegexOptions.Compiled);
private static readonly NadekoRandom rng = new NadekoRandom();
public static Dictionary<Regex, Func<Match, Task<string>>> regexPlaceholders = new Dictionary<Regex, Func<Match, Task<string>>>()
{
{ rngRegex, (match) => {
int from = 0;
int.TryParse(match.Groups["from"].ToString(), out from);
int to = 0;
int.TryParse(match.Groups["to"].ToString(), out to);
if(from == 0 && to == 0)
{
return Task.FromResult(rng.Next(0, 11).ToString());
}
if(from >= to)
return Task.FromResult(string.Empty);
return Task.FromResult(rng.Next(from,to+1).ToString());
} },
{ imgRegex, async (match) => {
var tag = match.Groups["tag"].ToString();
if(string.IsNullOrWhiteSpace(tag))
@ -94,31 +45,26 @@ namespace NadekoBot.Services.CustomReactions
} }
};
private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordShardedClient client)
private static string ResolveTriggerString(this string str, IUserMessage ctx, DiscordSocketClient client)
{
foreach (var ph in placeholders)
{
if (str.Contains(ph.Key))
str = str.ToLowerInvariant().Replace(ph.Key, ph.Value(ctx, client));
}
var rep = new ReplacementBuilder()
.WithUser(ctx.Author)
.WithClient(client)
.Build();
str = rep.Replace(str.ToLowerInvariant());
return str;
}
private static async Task<string> ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordShardedClient client, string resolvedTrigger)
private static async Task<string> ResolveResponseStringAsync(this string str, IUserMessage ctx, DiscordSocketClient client, string resolvedTrigger)
{
foreach (var ph in placeholders)
{
var lowerKey = ph.Key.ToLowerInvariant();
if (str.Contains(lowerKey))
str = str.Replace(lowerKey, ph.Value(ctx, client));
}
var rep = new ReplacementBuilder()
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client)
.WithOverride("%target%", () => ctx.Content.Substring(resolvedTrigger.Length).Trim())
.Build();
foreach (var ph in responsePlaceholders)
{
var lowerKey = ph.Key.ToLowerInvariant();
if (str.Contains(lowerKey))
str = str.Replace(lowerKey, ph.Value(ctx, resolvedTrigger));
}
str = rep.Replace(str);
foreach (var ph in regexPlaceholders)
{
@ -127,23 +73,30 @@ namespace NadekoBot.Services.CustomReactions
return str;
}
public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordShardedClient client)
public static string TriggerWithContext(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client)
=> cr.Trigger.ResolveTriggerString(ctx, client);
public static Task<string > ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordShardedClient client)
public static Task<string > ResponseWithContextAsync(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client)
=> cr.Response.ResolveResponseStringAsync(ctx, client, cr.Trigger.ResolveTriggerString(ctx, client));
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage context, DiscordShardedClient client, CustomReactionsService crs)
public static async Task<IUserMessage> Send(this CustomReaction cr, IUserMessage ctx, DiscordSocketClient client, CustomReactionsService crs)
{
var channel = cr.DmResponse ? await context.Author.CreateDMChannelAsync() : context.Channel;
var channel = cr.DmResponse ? await ctx.Author.GetOrCreateDMChannelAsync() : ctx.Channel;
crs.ReactionStats.AddOrUpdate(cr.Trigger, 1, (k, old) => ++old);
if (CREmbed.TryParse(cr.Response, out CREmbed crembed))
{
return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText ?? "");
var rep = new ReplacementBuilder()
.WithDefault(ctx.Author, ctx.Channel, (ctx.Channel as ITextChannel)?.Guild, client)
.WithOverride("%target%", () => ctx.Content.Substring(cr.Trigger.ResolveTriggerString(ctx, client).Length).Trim())
.Build();
rep.Replace(crembed);
return await channel.EmbedAsync(crembed.ToEmbed(), crembed.PlainText?.SanitizeMentions() ?? "");
}
return await channel.SendMessageAsync((await cr.ResponseWithContextAsync(context, client)).SanitizeMentions());
return await channel.SendMessageAsync((await cr.ResponseWithContextAsync(ctx, client)).SanitizeMentions());
}
}
}

View File

@ -12,7 +12,7 @@
public enum MusicType
{
Radio,
Normal,
YouTube,
Local,
Soundcloud
}

View File

@ -19,7 +19,9 @@ namespace NadekoBot.Services.Database
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlite("Filename=./data/NadekoBot.db");
return new NadekoContext(optionsBuilder.Options);
var ctx = new NadekoContext(optionsBuilder.Options);
ctx.Database.SetCommandTimeout(60);
return ctx;
}
}

View File

@ -5,6 +5,6 @@ namespace NadekoBot.Services.Database.Repositories
{
public interface IClashOfClansRepository : IRepository<ClashWar>
{
IEnumerable<ClashWar> GetAllWars();
IEnumerable<ClashWar> GetAllWars(List<long> guilds);
}
}

View File

@ -11,10 +11,10 @@ namespace NadekoBot.Services.Database.Repositories
GuildConfig For(ulong guildId, Func<DbSet<GuildConfig>, IQueryable<GuildConfig>> includes = null);
GuildConfig LogSettingsFor(ulong guildId);
IEnumerable<GuildConfig> OldPermissionsForAll();
IEnumerable<GuildConfig> GetAllGuildConfigs();
IEnumerable<FollowedStream> GetAllFollowedStreams();
IEnumerable<GuildConfig> GetAllGuildConfigs(List<long> availableGuilds);
IEnumerable<FollowedStream> GetAllFollowedStreams(List<long> included);
void SetCleverbotEnabled(ulong id, bool cleverbotEnabled);
IEnumerable<GuildConfig> Permissionsv2ForAll();
IEnumerable<GuildConfig> Permissionsv2ForAll(List<long> include);
GuildConfig GcWithPermissionsv2For(ulong guildId);
}
}

View File

@ -1,9 +1,11 @@
using NadekoBot.Services.Database.Models;
using System.Collections;
using System.Collections.Generic;
namespace NadekoBot.Services.Database.Repositories
{
public interface IReminderRepository : IRepository<Reminder>
{
IEnumerable<Reminder> GetIncludedReminders(List<long> guildIds);
}
}

View File

@ -7,5 +7,6 @@ namespace NadekoBot.Services.Database.Repositories
{
Warning[] For(ulong guildId, ulong userId);
Task ForgiveAll(ulong guildId, ulong userId, string mod);
Warning[] GetForGuild(ulong id);
}
}

View File

@ -11,9 +11,11 @@ namespace NadekoBot.Services.Database.Repositories.Impl
{
}
public IEnumerable<ClashWar> GetAllWars()
public IEnumerable<ClashWar> GetAllWars(List<long> guilds)
{
var toReturn = _set.Include(cw => cw.Bases)
var toReturn = _set
.Where(cw => guilds.Contains((long)cw.GuildId))
.Include(cw => cw.Bases)
.ToList();
toReturn.ForEach(cw => cw.Bases = cw.Bases.Where(w => w.SequenceNumber != null).OrderBy(w => w.SequenceNumber).ToList());
return toReturn;

View File

@ -24,8 +24,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
}
};
public IEnumerable<GuildConfig> GetAllGuildConfigs() =>
_set.Include(gc => gc.LogSetting)
public IEnumerable<GuildConfig> GetAllGuildConfigs(List<long> availableGuilds) =>
_set
.Where(gc => availableGuilds.Contains((long)gc.GuildId))
.Include(gc => gc.LogSetting)
.ThenInclude(ls => ls.IgnoredChannels)
.Include(gc => gc.MutedUsers)
.Include(gc => gc.CommandAliases)
@ -42,6 +44,7 @@ namespace NadekoBot.Services.Database.Repositories.Impl
.Include(gc => gc.SlowmodeIgnoredUsers)
.Include(gc => gc.AntiSpamSetting)
.ThenInclude(x => x.IgnoredChannels)
.Include(gc => gc.FollowedStreams)
.ToList();
/// <summary>
@ -134,9 +137,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
return query.ToList();
}
public IEnumerable<GuildConfig> Permissionsv2ForAll()
public IEnumerable<GuildConfig> Permissionsv2ForAll(List<long> include)
{
var query = _set
.Where(x => include.Contains((long)x.GuildId))
.Include(gc => gc.Permissions);
return query.ToList();
@ -167,8 +171,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
return config;
}
public IEnumerable<FollowedStream> GetAllFollowedStreams() =>
_set.Include(gc => gc.FollowedStreams)
public IEnumerable<FollowedStream> GetAllFollowedStreams(List<long> included) =>
_set
.Where(gc => included.Contains((long)gc.GuildId))
.Include(gc => gc.FollowedStreams)
.SelectMany(gc => gc.FollowedStreams)
.ToList();

View File

@ -1,5 +1,8 @@
using NadekoBot.Services.Database.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NadekoBot.Services.Database.Repositories.Impl
{
@ -8,5 +11,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
public ReminderRepository(DbContext context) : base(context)
{
}
public IEnumerable<Reminder> GetIncludedReminders(List<long> guildIds)
{
return _set.Where(x => guildIds.Contains((long)x.ServerId)).ToList();
}
}
}

View File

@ -2,6 +2,7 @@
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
using System;
namespace NadekoBot.Services.Database.Repositories.Impl
{
@ -32,5 +33,10 @@ namespace NadekoBot.Services.Database.Repositories.Impl
})
.ConfigureAwait(false);
}
public Warning[] GetForGuild(ulong id)
{
return _set.Where(x => x.GuildId == id).ToArray();
}
}
}

View File

@ -32,9 +32,21 @@ namespace NadekoBot.Services
public NadekoContext GetDbContext()
{
var context = new NadekoContext(options);
context.Database.SetCommandTimeout(60);
context.Database.Migrate();
context.EnsureSeedData();
//set important sqlite stuffs
var conn = context.Database.GetDbConnection();
conn.Open();
context.Database.ExecuteSqlCommand("PRAGMA journal_mode=WAL");
using (var com = conn.CreateCommand())
{
com.CommandText = "PRAGMA journal_mode=WAL; PRAGMA synchronous=OFF";
com.ExecuteNonQuery();
}
return context;
}

View File

@ -12,7 +12,7 @@ namespace NadekoBot.Services.Discord
public event Action<SocketReaction> OnReactionRemoved = delegate { };
public event Action OnReactionsCleared = delegate { };
public ReactionEventWrapper(DiscordShardedClient client, IUserMessage msg)
public ReactionEventWrapper(DiscordSocketClient client, IUserMessage msg)
{
Message = msg ?? throw new ArgumentNullException(nameof(msg));
_client = client;
@ -69,7 +69,7 @@ namespace NadekoBot.Services.Discord
}
private bool disposing = false;
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
public void Dispose()
{

View File

@ -15,19 +15,22 @@ namespace NadekoBot.Services.Games
{
public class ChatterBotService : IEarlyBlockingExecutor
{
private readonly DiscordShardedClient _client;
private readonly DiscordSocketClient _client;
private readonly Logger _log;
private readonly PermissionService _perms;
private readonly CommandHandler _cmd;
private readonly NadekoStrings _strings;
public ConcurrentDictionary<ulong, Lazy<ChatterBotSession>> ChatterBotGuilds { get; }
public ChatterBotService(DiscordShardedClient client, PermissionService perms, IEnumerable<GuildConfig> gcs, CommandHandler cmd)
public ChatterBotService(DiscordSocketClient client, PermissionService perms, IEnumerable<GuildConfig> gcs,
CommandHandler cmd, NadekoStrings strings)
{
_client = client;
_log = LogManager.GetCurrentClassLogger();
_perms = perms;
_cmd = cmd;
_strings = strings;
ChatterBotGuilds = new ConcurrentDictionary<ulong, Lazy<ChatterBotSession>>(
gcs.Where(gc => gc.CleverbotEnabled)
@ -83,7 +86,7 @@ namespace NadekoBot.Services.Games
return true;
}
public async Task<bool> TryExecuteEarly(DiscordShardedClient client, IGuild guild, IUserMessage usrMsg)
public async Task<bool> TryExecuteEarly(DiscordSocketClient client, IGuild guild, IUserMessage usrMsg)
{
if (!(guild is SocketGuild sg))
return false;
@ -101,8 +104,7 @@ namespace NadekoBot.Services.Games
{
if (pc.Verbose)
{
//todo move this to permissions
var returnMsg = $"Permission number #{index + 1} **{pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), sg)}** is preventing this action.";
var returnMsg = _strings.GetText("trigger", guild.Id, "Permissions".ToLowerInvariant(), index + 1, Format.Bold(pc.Permissions[index].GetCommand(_cmd.GetPrefix(guild), (SocketGuild)guild)));
try { await usrMsg.Channel.SendErrorAsync(returnMsg).ConfigureAwait(false); } catch { }
_log.Info(returnMsg);
}

Some files were not shown because too many files have changed in this diff Show More