diff --git a/NadekoBot/Classes/Extensions.cs b/NadekoBot/Classes/Extensions.cs
index cfbcb729..bf4374ad 100644
--- a/NadekoBot/Classes/Extensions.cs
+++ b/NadekoBot/Classes/Extensions.cs
@@ -8,6 +8,7 @@ using Discord;
 using NadekoBot.Modules;
 using System.IO;
 using System.Drawing;
+using NadekoBot.Classes;
 
 namespace NadekoBot.Extensions {
     public static class Extensions
@@ -152,7 +153,7 @@ namespace NadekoBot.Extensions {
         /// 
         /// 
         /// 
-        public static async Task ShortenUrl(this string str) => await Searches.ShortenUrl(str);
+        public static async Task ShortenUrl(this string str) => await SearchHelper.ShortenUrl(str);
 
         /// 
         /// Gets the program runtime
diff --git a/NadekoBot/Classes/Music/SoundCloud.cs b/NadekoBot/Classes/Music/SoundCloud.cs
index b8a95690..207e45e6 100644
--- a/NadekoBot/Classes/Music/SoundCloud.cs
+++ b/NadekoBot/Classes/Music/SoundCloud.cs
@@ -19,7 +19,7 @@ namespace NadekoBot.Classes.Music {
             if (string.IsNullOrWhiteSpace(NadekoBot.creds.SoundCloudClientID))
                 throw new ArgumentNullException(nameof(NadekoBot.creds.SoundCloudClientID));
 
-            var response = await Modules.Searches.GetResponseAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.creds.SoundCloudClientID}");
+            var response = await SearchHelper.GetResponseAsync($"http://api.soundcloud.com/resolve?url={url}&client_id={NadekoBot.creds.SoundCloudClientID}");
 
             var responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject(response);
             if (responseObj?.Kind != "track")
diff --git a/NadekoBot/Classes/Music/StreamRequest.cs b/NadekoBot/Classes/Music/StreamRequest.cs
index 1ee7692a..53fbd40d 100644
--- a/NadekoBot/Classes/Music/StreamRequest.cs
+++ b/NadekoBot/Classes/Music/StreamRequest.cs
@@ -78,7 +78,7 @@ namespace NadekoBot.Classes.Music {
 
                     if (OnResolving != null)
                         OnResolving();
-                    var links = await Searches.FindYoutubeUrlByKeywords(Query);
+                    var links = await SearchHelper.FindYoutubeUrlByKeywords(Query);
                     var allVideos = await YouTube.Default.GetAllVideosAsync(links);
                     var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
                     var video = videos
diff --git a/NadekoBot/Classes/PermissionCheckers/NSWFPermissionChecker.cs b/NadekoBot/Classes/PermissionCheckers/NSWFPermissionChecker.cs
new file mode 100644
index 00000000..6d22f09c
--- /dev/null
+++ b/NadekoBot/Classes/PermissionCheckers/NSWFPermissionChecker.cs
@@ -0,0 +1,18 @@
+using Discord.Commands.Permissions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+
+namespace NadekoBot.Classes.PermissionCheckers {
+    class NSFWPermissionChecker : PermissionChecker {
+        public override bool CanRun(Command command, User user, Channel channel, out string error) {
+            error = string.Empty;
+            Console.WriteLine(command.Category);
+            return false;
+        }
+    }
+}
diff --git a/NadekoBot/Classes/PermissionCheckers/PermissionChecker.cs b/NadekoBot/Classes/PermissionCheckers/PermissionChecker.cs
new file mode 100644
index 00000000..61fa4d7c
--- /dev/null
+++ b/NadekoBot/Classes/PermissionCheckers/PermissionChecker.cs
@@ -0,0 +1,17 @@
+using Discord.Commands.Permissions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Discord;
+using Discord.Commands;
+
+namespace NadekoBot.Classes.PermissionCheckers {
+    abstract class PermissionChecker : IPermissionChecker where T : new() {
+        public static readonly T _instance = new T();
+        public static T Instance => _instance;
+
+        public abstract bool CanRun(Command command, User user, Channel channel, out string error);
+    }
+}
diff --git a/NadekoBot/Classes/SearchHelper.cs b/NadekoBot/Classes/SearchHelper.cs
new file mode 100644
index 00000000..12197f55
--- /dev/null
+++ b/NadekoBot/Classes/SearchHelper.cs
@@ -0,0 +1,219 @@
+using NadekoBot.Extensions;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace NadekoBot.Classes {
+    static class SearchHelper {
+        public static async Task GetResponseStream(string v) {
+            var wr = (HttpWebRequest)WebRequest.Create(v);
+            try {
+                return (await (wr).GetResponseAsync()).GetResponseStream();
+            } catch (Exception ex) {
+                Console.WriteLine("error in getresponse stream " + ex);
+                return null;
+            }
+        }
+
+        public static async Task GetResponseAsync(string v) =>
+            await new StreamReader((await ((HttpWebRequest)WebRequest.Create(v)).GetResponseAsync()).GetResponseStream()).ReadToEndAsync();
+
+        public static async Task GetResponseAsync(string v, IEnumerable> headers) {
+            var wr = (HttpWebRequest)WebRequest.Create(v);
+            foreach (var header in headers) {
+                wr.Headers.Add(header.Item1, header.Item2);
+            }
+            return await new StreamReader((await wr.GetResponseAsync()).GetResponseStream()).ReadToEndAsync();
+        }
+
+        private static string token = "";
+        public static async Task GetAnimeQueryResultLink(string query) {
+            try {
+                var cl = new RestSharp.RestClient("http://anilist.co/api");
+                var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST);
+
+                RefreshAnilistToken();
+
+                rq = new RestSharp.RestRequest("/anime/search/" + Uri.EscapeUriString(query));
+                rq.AddParameter("access_token", token);
+
+                var smallObj = JArray.Parse(cl.Execute(rq).Content)[0];
+
+                rq = new RestSharp.RestRequest("anime/" + smallObj["id"]);
+                rq.AddParameter("access_token", token);
+                return await Task.Run(() => JsonConvert.DeserializeObject(cl.Execute(rq).Content));
+            } catch (Exception) {
+                return null;
+            }
+        }
+        //todo kick out RestSharp and make it truly async
+        public static async Task GetMangaQueryResultLink(string query) {
+            try {
+                RefreshAnilistToken();
+
+                var cl = new RestSharp.RestClient("http://anilist.co/api");
+                var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST);
+                rq = new RestSharp.RestRequest("/manga/search/" + Uri.EscapeUriString(query));
+                rq.AddParameter("access_token", token);
+
+                var smallObj = JArray.Parse(cl.Execute(rq).Content)[0];
+
+                rq = new RestSharp.RestRequest("manga/" + smallObj["id"]);
+                rq.AddParameter("access_token", token);
+                return await Task.Run(() => JsonConvert.DeserializeObject(cl.Execute(rq).Content));
+            } catch (Exception ex) {
+                Console.WriteLine(ex.ToString());
+                return null;
+            }
+        }
+
+        private static void RefreshAnilistToken() {
+            try {
+                var cl = new RestSharp.RestClient("http://anilist.co/api");
+                var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST);
+                rq.AddParameter("grant_type", "client_credentials");
+                rq.AddParameter("client_id", "kwoth-w0ki9");
+                rq.AddParameter("client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z");
+                var exec = cl.Execute(rq);
+
+                token = JObject.Parse(exec.Content)["access_token"].ToString();
+            } catch (Exception ex) {
+                Console.WriteLine($"Failed refreshing anilist token:\n {ex}");
+            }
+        }
+
+        public static async Task ValidateQuery(Discord.Channel ch, string query) {
+            if (string.IsNullOrEmpty(query.Trim())) {
+                await ch.Send("Please specify search parameters.");
+                return false;
+            }
+            return true;
+        }
+
+        public static async Task FindYoutubeUrlByKeywords(string v) {
+            if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) {
+                Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`.");
+                return @"https://www.youtube.com/watch?v=dQw4w9WgXcQ";
+            }
+            try {
+                //maybe it is already a youtube url, in which case we will just extract the id and prepend it with youtube.com?v=
+                var match = new Regex("(?:youtu\\.be\\/|v=)(?[\\da-zA-Z\\-_]*)").Match(v);
+                if (match.Length > 1) {
+                    string str = $"http://www.youtube.com?v={ match.Groups["id"].Value }";
+                    return str;
+                }
+
+                WebRequest wr = WebRequest.Create("https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q=" + Uri.EscapeDataString(v) + "&key=" + NadekoBot.GoogleAPIKey);
+
+                var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream());
+
+                dynamic obj = JObject.Parse(await sr.ReadToEndAsync());
+                return "http://www.youtube.com/watch?v=" + obj.items[0].id.videoId.ToString();
+            } catch (Exception ex) {
+                Console.WriteLine($"Error in findyoutubeurl: {ex.Message}");
+                return string.Empty;
+            }
+        }
+
+        public static async Task GetPlaylistIdByKeyword(string v) {
+            if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) {
+                Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`.");
+                return string.Empty;
+            }
+            try {
+                WebRequest wr = WebRequest.Create($"https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q={Uri.EscapeDataString(v)}&type=playlist&key={NadekoBot.creds.GoogleAPIKey}");
+
+                var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream());
+
+                dynamic obj = JObject.Parse(await sr.ReadToEndAsync());
+                return obj.items[0].id.playlistId.ToString();
+            } catch (Exception ex) {
+                Console.WriteLine($"Error in GetPlaylistId: {ex.Message}");
+                return string.Empty;
+            }
+        }
+
+        public static async Task> GetVideoIDs(string v) {
+            List toReturn = new List();
+            if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) {
+                Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`.");
+                return toReturn;
+            }
+            try {
+
+                WebRequest wr = WebRequest.Create($"https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails&maxResults={25}&playlistId={v}&key={ NadekoBot.creds.GoogleAPIKey }");
+
+                var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream());
+
+                dynamic obj = JObject.Parse(await sr.ReadToEndAsync());
+
+                foreach (var item in obj.items) {
+                    toReturn.Add("http://www.youtube.com/watch?v=" + item.contentDetails.videoId);
+                }
+                return toReturn;
+            } catch (Exception ex) {
+                Console.WriteLine($"Error in GetPlaylistId: {ex.Message}");
+                return new List();
+            }
+        }
+
+
+        public static async Task GetDanbooruImageLink(string tag) {
+            try {
+                var rng = new Random();
+
+                if (tag == "loli") //loli doesn't work for some reason atm
+                    tag = "flat_chest";
+
+                var webpage = await GetResponseAsync($"http://danbooru.donmai.us/posts?page={ rng.Next(0, 30) }&tags={ tag.Replace(" ", "_") }");
+                var matches = Regex.Matches(webpage, "data-large-file-url=\"(?.*?)\"");
+
+                return await $"http://danbooru.donmai.us{ matches[rng.Next(0, matches.Count)].Groups["id"].Value }".ShortenUrl();
+            } catch (Exception) {
+                return null;
+            }
+        }
+
+        public static async Task GetGelbooruImageLink(string tag) {
+            try {
+                var rng = new Random();
+                var url = $"http://gelbooru.com/index.php?page=post&s=list&pid={ rng.Next(0, 15) * 42 }&tags={ tag.Replace(" ", "_") }";
+                var webpage = await GetResponseAsync(url); // first extract the post id and go to that posts page
+                var matches = Regex.Matches(webpage, "span id=\"s(?\\d*)\"");
+                var postLink = $"http://gelbooru.com/index.php?page=post&s=view&id={ matches[rng.Next(0, matches.Count)].Groups["id"].Value }";
+                webpage = await GetResponseAsync(postLink);
+                //now extract the image from post page
+                var match = Regex.Match(webpage, "\"(?http://simg4.gelbooru.com//images.*?)\"");
+                return match.Groups["url"].Value;
+            } catch (Exception) {
+                return null;
+            }
+        }
+
+        public static async Task ShortenUrl(string url) {
+            if (NadekoBot.GoogleAPIKey == null || NadekoBot.GoogleAPIKey == "") return url;
+            try {
+                var httpWebRequest = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/urlshortener/v1/url?key=" + NadekoBot.GoogleAPIKey);
+                httpWebRequest.ContentType = "application/json";
+                httpWebRequest.Method = "POST";
+
+                using (var streamWriter = new StreamWriter(await httpWebRequest.GetRequestStreamAsync())) {
+                    string json = "{\"longUrl\":\"" + url + "\"}";
+                    streamWriter.Write(json);
+                }
+
+                var httpResponse = (await httpWebRequest.GetResponseAsync()) as HttpWebResponse;
+                using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) {
+                    string responseText = await streamReader.ReadToEndAsync();
+                    string MATCH_PATTERN = @"""id"": ?""(?.+)""";
+                    return Regex.Match(responseText, MATCH_PATTERN).Groups["id"].Value;
+                }
+            } catch (Exception ex) { Console.WriteLine(ex.ToString()); return ""; }
+        }
+    }
+}
diff --git a/NadekoBot/Modules/Music.cs b/NadekoBot/Modules/Music.cs
index f78f6c62..31f2c3e7 100644
--- a/NadekoBot/Modules/Music.cs
+++ b/NadekoBot/Modules/Music.cs
@@ -8,6 +8,7 @@ using System.Collections.Concurrent;
 using NadekoBot.Classes.Music;
 using Timer = System.Timers.Timer;
 using System.Threading.Tasks;
+using NadekoBot.Classes;
 
 namespace NadekoBot.Modules {
     class Music : DiscordModule {
@@ -172,7 +173,7 @@ namespace NadekoBot.Modules {
                             await e.Send("💢 You need to be in the voice channel on this server.");
                             return;
                         }
-                        var ids = await Searches.GetVideoIDs(await Searches.GetPlaylistIdByKeyword(e.GetArg("playlist")));
+                        var ids = await SearchHelper.GetVideoIDs(await SearchHelper.GetPlaylistIdByKeyword(e.GetArg("playlist")));
                         //todo TEMPORARY SOLUTION, USE RESOLVE QUEUE IN THE FUTURE
                         var msg = await e.Send($"🎵 Attempting to queue **{ids.Count}** songs".SnPl(ids.Count));
                         foreach (var id in ids) {
diff --git a/NadekoBot/Modules/NSFW.cs b/NadekoBot/Modules/NSFW.cs
new file mode 100644
index 00000000..44ca9d25
--- /dev/null
+++ b/NadekoBot/Modules/NSFW.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Discord.Modules;
+using NadekoBot.Extensions;
+using NadekoBot.Classes.PermissionCheckers;
+using Discord.Commands;
+using Newtonsoft.Json.Linq;
+using NadekoBot.Classes;
+
+namespace NadekoBot.Modules {
+    class NSFW : DiscordModule {
+
+        private Random _r = new Random();
+
+        public NSFW() : base() {
+
+        }
+
+        public override void Install(ModuleManager manager) {
+            manager.CreateCommands("", cgb => {
+                cgb.CreateCommand("~hentai")
+                    .Description("Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered.\n**Usage**: ~hentai yuri")
+                    .Parameter("tag", ParameterType.Unparsed)
+                    .Do(async e => {
+                        string tag = e.GetArg("tag");
+                        if (tag == null)
+                            tag = "";
+                        await e.Send(":heart: Gelbooru: " + await SearchHelper.GetGelbooruImageLink(tag));
+                        await e.Send(":heart: Danbooru: " + await SearchHelper.GetDanbooruImageLink(tag));
+                    });
+                cgb.CreateCommand("~danbooru")
+                    .Description("Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered.\n**Usage**: ~hentai yuri")
+                    .Parameter("tag", ParameterType.Unparsed)
+                    .Do(async e => {
+                        string tag = e.GetArg("tag");
+                        if (tag == null)
+                            tag = "";
+                        await e.Send(await SearchHelper.GetDanbooruImageLink(tag));
+                    });
+                cgb.CreateCommand("~gelbooru")
+                    .Description("Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered.\n**Usage**: ~hentai yuri")
+                    .Parameter("tag", ParameterType.Unparsed)
+                    .Do(async e => {
+                        string tag = e.GetArg("tag");
+                        if (tag == null)
+                            tag = "";
+                        await e.Send(await SearchHelper.GetGelbooruImageLink(tag));
+                    });
+                cgb.CreateCommand("~cp")
+                    .Description("We all know where this will lead you to.")
+                    .Parameter("anything", ParameterType.Unparsed)
+                    .Do(async e => {
+                        await e.Send("http://i.imgur.com/MZkY1md.jpg");
+                    });
+                cgb.CreateCommand("~boobs")
+                    .Description("Real adult content.")
+                    .Do(async e => {
+                        try {
+                            var obj = JArray.Parse(await SearchHelper.GetResponseAsync($"http://api.oboobs.ru/boobs/{_r.Next(0, 9304)}"))[0];
+                            await e.Send($"http://media.oboobs.ru/{ obj["preview"].ToString() }");
+                        } catch (Exception ex) {
+                            await e.Send($"💢 {ex.Message}");
+                        }
+                    });
+            });
+        }
+    }
+}
diff --git a/NadekoBot/Modules/Searches.cs b/NadekoBot/Modules/Searches.cs
index 41e1b370..bb493797 100644
--- a/NadekoBot/Modules/Searches.cs
+++ b/NadekoBot/Modules/Searches.cs
@@ -9,6 +9,8 @@ using Newtonsoft.Json;
 using Discord.Commands;
 using NadekoBot.Extensions;
 using System.Collections.Generic;
+using NadekoBot.Classes.PermissionCheckers;
+using NadekoBot.Classes;
 
 namespace NadekoBot.Modules {
     class Searches : DiscordModule {
@@ -29,9 +31,9 @@ namespace NadekoBot.Modules {
                     .Parameter("query", ParameterType.Unparsed)
                     .Description("Searches youtubes and shows the first result")
                     .Do(async e => {
-                        if (!(await ValidateQuery(e.Channel, e.GetArg("query")))) return;
+                        if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")))) return;
 
-                        var str = await ShortenUrl(await FindYoutubeUrlByKeywords(e.GetArg("query")));
+                        var str = await SearchHelper.ShortenUrl(await SearchHelper.FindYoutubeUrlByKeywords(e.GetArg("query")));
                         if (string.IsNullOrEmpty(str.Trim())) {
                             await e.Send("Query failed");
                             return;
@@ -44,9 +46,9 @@ namespace NadekoBot.Modules {
                     .Parameter("query", ParameterType.Unparsed)
                     .Description("Queries anilist for an anime and shows the first result.")
                     .Do(async e => {
-                        if (!(await ValidateQuery(e.Channel, e.GetArg("query")))) return;
+                        if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")))) return;
 
-                        var result = await GetAnimeQueryResultLink(e.GetArg("query"));
+                        var result = await SearchHelper.GetAnimeQueryResultLink(e.GetArg("query"));
                         if (result == null) {
                             await e.Send("Failed to find that anime.");
                             return;
@@ -60,9 +62,9 @@ namespace NadekoBot.Modules {
                     .Parameter("query", ParameterType.Unparsed)
                     .Description("Queries anilist for a manga and shows the first result.")
                     .Do(async e => {
-                        if (!(await ValidateQuery(e.Channel, e.GetArg("query")))) return;
+                        if (!(await SearchHelper.ValidateQuery(e.Channel, e.GetArg("query")))) return;
 
-                        var result = await GetMangaQueryResultLink(e.GetArg("query"));
+                        var result = await SearchHelper.GetMangaQueryResultLink(e.GetArg("query"));
                         if (result == null) {
                             await e.Send("Failed to find that anime.");
                             return;
@@ -90,7 +92,7 @@ namespace NadekoBot.Modules {
                                return;
                            try {
                                var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&fields=items%2Flink&key={NadekoBot.creds.GoogleAPIKey}";
-                               var obj = JObject.Parse(await GetResponseAsync(reqString));
+                               var obj = JObject.Parse(await SearchHelper.GetResponseAsync(reqString));
                                await e.Send(obj["items"][0]["link"].ToString());
                            } catch (Exception ex) {
                                await e.Send($"💢 {ex.Message}");
@@ -105,57 +107,12 @@ namespace NadekoBot.Modules {
                                return;
                            try {
                                var reqString = $"https://www.googleapis.com/customsearch/v1?q={Uri.EscapeDataString(e.GetArg("query"))}&cx=018084019232060951019%3Ahs5piey28-e&num=1&searchType=image&start={ _r.Next(1, 150) }&fields=items%2Flink&key={NadekoBot.creds.GoogleAPIKey}";
-                               var obj = JObject.Parse(await GetResponseAsync(reqString));
+                               var obj = JObject.Parse(await SearchHelper.GetResponseAsync(reqString));
                                await e.Send(obj["items"][0]["link"].ToString());
                            } catch (Exception ex) {
                                await e.Send($"💢 {ex.Message}");
                            }
                        });
-
-                cgb.CreateCommand("~hentai")
-                    .Description("Shows a random NSFW hentai image from gelbooru and danbooru with a given tag. Tag is optional but preffered.\n**Usage**: ~hentai yuri")
-                    .Parameter("tag", ParameterType.Unparsed)
-                    .Do(async e => {
-                        string tag = e.GetArg("tag");
-                        if (tag == null)
-                            tag = "";
-                        await e.Send(":heart: Gelbooru: " + await GetGelbooruImageLink(tag));
-                        await e.Send(":heart: Danbooru: " + await GetDanbooruImageLink(tag));
-                    });
-                cgb.CreateCommand("~danbooru")
-                    .Description("Shows a random hentai image from danbooru with a given tag. Tag is optional but preffered.\n**Usage**: ~hentai yuri")
-                    .Parameter("tag", ParameterType.Unparsed)
-                    .Do(async e => {
-                        string tag = e.GetArg("tag");
-                        if (tag == null)
-                            tag = "";
-                        await e.Send(await GetDanbooruImageLink(tag));
-                    });
-                cgb.CreateCommand("~gelbooru")
-                    .Description("Shows a random hentai image from gelbooru with a given tag. Tag is optional but preffered.\n**Usage**: ~hentai yuri")
-                    .Parameter("tag", ParameterType.Unparsed)
-                    .Do(async e => {
-                        string tag = e.GetArg("tag");
-                        if (tag == null)
-                            tag = "";
-                        await e.Send(await GetGelbooruImageLink(tag));
-                    });
-                cgb.CreateCommand("~cp")
-                    .Description("We all know where this will lead you to.")
-                    .Parameter("anything", ParameterType.Unparsed)
-                    .Do(async e => {
-                        await e.Send("http://i.imgur.com/MZkY1md.jpg");
-                    });
-                cgb.CreateCommand("~boobs")
-                    .Description("Real adult content.")
-                    .Do(async e => {
-                        try {
-                            var obj = JArray.Parse(await GetResponseAsync($"http://api.oboobs.ru/boobs/{_r.Next(0, 9304)}"))[0];
-                            await e.Send($"http://media.oboobs.ru/{ obj["preview"].ToString() }");
-                        } catch (Exception ex) {
-                            await e.Send($"💢 {ex.Message}");
-                        }
-                    });
                 cgb.CreateCommand("lmgtfy")
                     .Alias("~lmgtfy")
                     .Description("Google something for an idiot.")
@@ -175,7 +132,7 @@ namespace NadekoBot.Modules {
                           return;
                       }
                       await e.Channel.SendIsTyping();
-                      var res = await GetResponseAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}",
+                      var res = await SearchHelper.GetResponseAsync($"https://omgvamp-hearthstone-v1.p.mashape.com/cards/search/{Uri.EscapeUriString(arg)}",
                           new Tuple[] {
                               new Tuple("X-Mashape-Key", NadekoBot.creds.MashapeKey),
                           });
@@ -192,7 +149,7 @@ namespace NadekoBot.Modules {
                               if (!item.HasValues || item["img"] == null)
                                   continue;
                               cnt++;
-                              images.Add(System.Drawing.Bitmap.FromStream(await GetResponseStream(item["img"].ToString())));
+                              images.Add(System.Drawing.Bitmap.FromStream(await SearchHelper.GetResponseStream(item["img"].ToString())));
                           }
                           if (items.Count > 4) {
                               await e.Send("⚠ Found over 4 images. Showing random 4.");
@@ -246,216 +203,5 @@ namespace NadekoBot.Modules {
                 */
             });
         }
-
-        public static async Task GetResponseStream(string v) {
-            var wr = (HttpWebRequest)WebRequest.Create(v);
-            try {
-                return (await (wr).GetResponseAsync()).GetResponseStream();
-            } catch (Exception ex) {
-                Console.WriteLine("error in getresponse stream " + ex);
-                return null;
-            }
-        }
-
-        public static async Task GetResponseAsync(string v) =>
-            await new StreamReader((await ((HttpWebRequest)WebRequest.Create(v)).GetResponseAsync()).GetResponseStream()).ReadToEndAsync();
-
-        public static async Task GetResponseAsync(string v, IEnumerable> headers) {
-            var wr = (HttpWebRequest)WebRequest.Create(v);
-            foreach (var header in headers) {
-                wr.Headers.Add(header.Item1, header.Item2);
-            }
-            return await new StreamReader((await wr.GetResponseAsync()).GetResponseStream()).ReadToEndAsync();
-        }
-
-        private string token = "";
-        private async Task GetAnimeQueryResultLink(string query) {
-            try {
-                var cl = new RestSharp.RestClient("http://anilist.co/api");
-                var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST);
-
-                RefreshAnilistToken();
-
-                rq = new RestSharp.RestRequest("/anime/search/" + Uri.EscapeUriString(query));
-                rq.AddParameter("access_token", token);
-
-                var smallObj = JArray.Parse(cl.Execute(rq).Content)[0];
-
-                rq = new RestSharp.RestRequest("anime/" + smallObj["id"]);
-                rq.AddParameter("access_token", token);
-                return await Task.Run(() => JsonConvert.DeserializeObject(cl.Execute(rq).Content));
-            } catch (Exception) {
-                return null;
-            }
-        }
-        //todo kick out RestSharp and make it truly async
-        private async Task GetMangaQueryResultLink(string query) {
-            try {
-                RefreshAnilistToken();
-
-                var cl = new RestSharp.RestClient("http://anilist.co/api");
-                var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST);
-                rq = new RestSharp.RestRequest("/manga/search/" + Uri.EscapeUriString(query));
-                rq.AddParameter("access_token", token);
-
-                var smallObj = JArray.Parse(cl.Execute(rq).Content)[0];
-
-                rq = new RestSharp.RestRequest("manga/" + smallObj["id"]);
-                rq.AddParameter("access_token", token);
-                return await Task.Run(() => JsonConvert.DeserializeObject(cl.Execute(rq).Content));
-            } catch (Exception ex) {
-                Console.WriteLine(ex.ToString());
-                return null;
-            }
-        }
-
-        private void RefreshAnilistToken() {
-            try {
-                var cl = new RestSharp.RestClient("http://anilist.co/api");
-                var rq = new RestSharp.RestRequest("/auth/access_token", RestSharp.Method.POST);
-                rq.AddParameter("grant_type", "client_credentials");
-                rq.AddParameter("client_id", "kwoth-w0ki9");
-                rq.AddParameter("client_secret", "Qd6j4FIAi1ZK6Pc7N7V4Z");
-                var exec = cl.Execute(rq);
-                /*
-                Console.WriteLine($"Server gave me content: { exec.Content }\n{ exec.ResponseStatus } -> {exec.ErrorMessage} ");
-                Console.WriteLine($"Err exception: {exec.ErrorException}");
-                Console.WriteLine($"Inner: {exec.ErrorException.InnerException}");
-                */
-
-                token = JObject.Parse(exec.Content)["access_token"].ToString();
-            } catch (Exception ex) {
-                Console.WriteLine($"Failed refreshing anilist token:\n {ex}");
-            }
-        }
-
-        private static async Task ValidateQuery(Discord.Channel ch, string query) {
-            if (string.IsNullOrEmpty(query.Trim())) {
-                await ch.Send("Please specify search parameters.");
-                return false;
-            }
-            return true;
-        }
-
-        public static async Task FindYoutubeUrlByKeywords(string v) {
-            if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) {
-                Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`.");
-                return @"https://www.youtube.com/watch?v=dQw4w9WgXcQ";
-            }
-            try {
-                //maybe it is already a youtube url, in which case we will just extract the id and prepend it with youtube.com?v=
-                var match = new Regex("(?:youtu\\.be\\/|v=)(?[\\da-zA-Z\\-_]*)").Match(v);
-                if (match.Length > 1) {
-                    string str = $"http://www.youtube.com?v={ match.Groups["id"].Value }";
-                    return str;
-                }
-
-                WebRequest wr = WebRequest.Create("https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q=" + Uri.EscapeDataString(v) + "&key=" + NadekoBot.GoogleAPIKey);
-
-                var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream());
-
-                dynamic obj = JObject.Parse(await sr.ReadToEndAsync());
-                return "http://www.youtube.com/watch?v=" + obj.items[0].id.videoId.ToString();
-            } catch (Exception ex) {
-                Console.WriteLine($"Error in findyoutubeurl: {ex.Message}");
-                return string.Empty;
-            }
-        }
-
-        public static async Task GetPlaylistIdByKeyword(string v) {
-            if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) {
-                Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`.");
-                return string.Empty;
-            }
-            try {
-                WebRequest wr = WebRequest.Create($"https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=1&q={Uri.EscapeDataString(v)}&type=playlist&key={NadekoBot.creds.GoogleAPIKey}");
-
-                var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream());
-
-                dynamic obj = JObject.Parse(await sr.ReadToEndAsync());
-                return obj.items[0].id.playlistId.ToString();
-            } catch (Exception ex) {
-                Console.WriteLine($"Error in GetPlaylistId: {ex.Message}");
-                return string.Empty;
-            }
-        }
-
-        public static async Task> GetVideoIDs(string v) {
-            List toReturn = new List();
-            if (NadekoBot.GoogleAPIKey == "" || NadekoBot.GoogleAPIKey == null) {
-                Console.WriteLine("ERROR: No google api key found. Playing `Never gonna give you up`.");
-                return toReturn;
-            }
-            try {
-
-                WebRequest wr = WebRequest.Create($"https://www.googleapis.com/youtube/v3/playlistItems?part=contentDetails&maxResults={25}&playlistId={v}&key={ NadekoBot.creds.GoogleAPIKey }");
-
-                var sr = new StreamReader((await wr.GetResponseAsync()).GetResponseStream());
-
-                dynamic obj = JObject.Parse(await sr.ReadToEndAsync());
-
-                foreach (var item in obj.items) {
-                    toReturn.Add("http://www.youtube.com/watch?v=" + item.contentDetails.videoId);
-                }
-                return toReturn;
-            } catch (Exception ex) {
-                Console.WriteLine($"Error in GetPlaylistId: {ex.Message}");
-                return new List();
-            }
-        }
-
-
-        public async Task GetDanbooruImageLink(string tag) {
-            try {
-                var rng = new Random();
-
-                if (tag == "loli") //loli doesn't work for some reason atm
-                    tag = "flat_chest";
-
-                var webpage = await GetResponseAsync($"http://danbooru.donmai.us/posts?page={ rng.Next(0, 30) }&tags={ tag.Replace(" ", "_") }");
-                var matches = Regex.Matches(webpage, "data-large-file-url=\"(?.*?)\"");
-
-                return await $"http://danbooru.donmai.us{ matches[rng.Next(0, matches.Count)].Groups["id"].Value }".ShortenUrl();
-            } catch (Exception) {
-                return null;
-            }
-        }
-
-        public async Task GetGelbooruImageLink(string tag) {
-            try {
-                var rng = new Random();
-                var url = $"http://gelbooru.com/index.php?page=post&s=list&pid={ rng.Next(0, 15) * 42 }&tags={ tag.Replace(" ", "_") }";
-                var webpage = await GetResponseAsync(url); // first extract the post id and go to that posts page
-                var matches = Regex.Matches(webpage, "span id=\"s(?\\d*)\"");
-                var postLink = $"http://gelbooru.com/index.php?page=post&s=view&id={ matches[rng.Next(0, matches.Count)].Groups["id"].Value }";
-                webpage = await GetResponseAsync(postLink);
-                //now extract the image from post page
-                var match = Regex.Match(webpage, "\"(?http://simg4.gelbooru.com//images.*?)\"");
-                return match.Groups["url"].Value;
-            } catch (Exception) {
-                return null;
-            }
-        }
-
-        public static async Task ShortenUrl(string url) {
-            if (NadekoBot.GoogleAPIKey == null || NadekoBot.GoogleAPIKey == "") return url;
-            try {
-                var httpWebRequest = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/urlshortener/v1/url?key=" + NadekoBot.GoogleAPIKey);
-                httpWebRequest.ContentType = "application/json";
-                httpWebRequest.Method = "POST";
-
-                using (var streamWriter = new StreamWriter(await httpWebRequest.GetRequestStreamAsync())) {
-                    string json = "{\"longUrl\":\"" + url + "\"}";
-                    streamWriter.Write(json);
-                }
-
-                var httpResponse = (await httpWebRequest.GetResponseAsync()) as HttpWebResponse;
-                using (var streamReader = new StreamReader(httpResponse.GetResponseStream())) {
-                    string responseText = await streamReader.ReadToEndAsync();
-                    string MATCH_PATTERN = @"""id"": ?""(?.+)""";
-                    return Regex.Match(responseText, MATCH_PATTERN).Groups["id"].Value;
-                }
-            } catch (Exception ex) { Console.WriteLine(ex.ToString()); return ""; }
-        }
     }
 }
diff --git a/NadekoBot/NadekoBot.cs b/NadekoBot/NadekoBot.cs
index 0803f8cb..a5f03d62 100644
--- a/NadekoBot/NadekoBot.cs
+++ b/NadekoBot/NadekoBot.cs
@@ -95,6 +95,7 @@ namespace NadekoBot {
             modules.Add(new Searches(), "Searches", ModuleFilter.None);
             if (loadTrello)
                 modules.Add(new Trello(), "Trello", ModuleFilter.None);
+            modules.Add(new NSFW(), "NSFW", ModuleFilter.None);
 
             //run the bot
             client.ExecuteAndWait(async () => {
diff --git a/NadekoBot/NadekoBot.csproj b/NadekoBot/NadekoBot.csproj
index 3b22dc87..90ce03c9 100644
--- a/NadekoBot/NadekoBot.csproj
+++ b/NadekoBot/NadekoBot.csproj
@@ -132,6 +132,9 @@
     
     
     
+    
+    
+    
     
     
     
@@ -164,6 +167,7 @@
     
     
     
+