├── .env ├── .gitattributes ├── .gitignore ├── LICENSE ├── hentai_func.py ├── hentaibot.py └── requirements.py /.env: -------------------------------------------------------------------------------- 1 | token=enter bot token here -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | #databases 132 | database.db 133 | logging.db 134 | 135 | #files 136 | /assets/media.py 137 | *.db 138 | cmd-invoke-errors.txt 139 | voter_data.json 140 | assets/Images/ 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 박현 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /hentai_func.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from random import choice, shuffle 3 | from typing import Optional 4 | import aiohttp 5 | from requests import get 6 | 7 | 8 | class NsfwApis(Enum): 9 | Rule34Api = "https://api.rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&limit=1000&tags=rating:explicit+" 10 | KonachanApi = "https://konachan.com/post.json?s=post&q=index&limit=100&tags=rating:explicit+" 11 | YandereApi = "https://yande.re/post.json?limit=100&tags=rating:explicit+" 12 | GelbooruApi = "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1&limit=100&tags=rating:explicit+" 13 | DanbooruApi = "https://danbooru.donmai.us/posts.json?limit=100&tags=rating:explicit+" 14 | 15 | 16 | class NekosFunTags(Enum): 17 | anal = "anal" 18 | blowjob = "bj" 19 | cum = "cum" 20 | hentai = "hentai" 21 | yuri = "lesbian" 22 | 23 | 24 | class Hentai: 25 | def __init__(self): 26 | self.blacklisted_tags = {"loli", "shota", "cub"} 27 | 28 | def format_tags(self, tags: str = None): 29 | if tags: 30 | tags = [ 31 | tag.strip().replace(" ", "_") 32 | for tag in tags.split(",") 33 | if tag.strip().replace(" ", "_") 34 | ] 35 | tags_string = "+".join(tags) 36 | return tags_string 37 | else: 38 | return "" 39 | 40 | async def get_nsfw_image(self, provider: NsfwApis, tags: Optional[str] = None): 41 | tags = tags.lower() if tags else None 42 | 43 | url = provider.value + self.format_tags(tags) 44 | 45 | async with aiohttp.ClientSession() as session: 46 | async with session.get(url) as resp: 47 | nsfw_images = await resp.json() 48 | 49 | if not nsfw_images: 50 | return None 51 | 52 | if provider.value == NsfwApis.GelbooruApi.value: 53 | nsfw_images_list = list(nsfw_images.get("post", [])) 54 | else: 55 | nsfw_images_list = list(nsfw_images) 56 | 57 | shuffle(nsfw_images_list) 58 | 59 | if not tags: 60 | tags = "" 61 | 62 | tags_list = [ 63 | tag.strip().replace(" ", "_") 64 | for tag in tags.split(",") 65 | if tag.strip().replace(" ", "_") not in self.blacklisted_tags 66 | ] 67 | 68 | if len(tags_list) == 0 or len(tags_list) > 3: 69 | return None 70 | 71 | filtered_ret = [ 72 | img 73 | for img in nsfw_images_list 74 | if all( 75 | tag in img["tag_string"] if provider is NsfwApis.DanbooruApi else tag in img["tags"] 76 | for tag in tags_list 77 | ) 78 | ] 79 | 80 | if len(filtered_ret) == 0: 81 | return None 82 | 83 | filtered_images = [] 84 | for image in filtered_ret: 85 | tags = ( 86 | image["tag_string"].lower().split(" ") 87 | if provider is NsfwApis.DanbooruApi 88 | else image["tags"].lower().split(" ") 89 | ) 90 | if any(tag in self.blacklisted_tags for tag in tags): 91 | continue 92 | filtered_images.append(image) 93 | return choice(filtered_images) 94 | 95 | 96 | @staticmethod 97 | def nekofun(endpoint: str): 98 | r = get("http://api.nekos.fun:8080/api/" + endpoint) 99 | if r.status_code != 200: 100 | return False 101 | else: 102 | return str(r.json()["image"]) 103 | 104 | @staticmethod 105 | def boobchi(): 106 | r = get("https://bocchi-api.vercel.app/JSON") 107 | if r.status_code != 200: 108 | return False 109 | else: 110 | image=str(r.json()["imgURL"]) 111 | source=str(r.json()["sauceURL"]) 112 | return image, source 113 | -------------------------------------------------------------------------------- /hentaibot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | from hentai_func import Hentai, NekosFunTags, NsfwApis 4 | import discord 5 | from discord import app_commands 6 | from typing import Optional 7 | from dotenv import load_dotenv 8 | from datetime import datetime 9 | 10 | 11 | class Bot(discord.Client): 12 | def __init__(self, intents: discord.Intents): 13 | super().__init__(intents=intents) 14 | self.tree = app_commands.CommandTree(self) 15 | 16 | async def setup_hook(self): 17 | await self.tree.sync() 18 | 19 | 20 | intents = discord.Intents(messages=True, members=True, typing=True, guilds=True) 21 | bot = Bot(intents=intents) 22 | game = discord.Game("Naughty") 23 | 24 | load_dotenv() 25 | token = os.getenv("token") 26 | 27 | 28 | @bot.event 29 | async def on_ready(): 30 | print(bot.user.name) 31 | await bot.change_presence(status=discord.Status.idle, activity=game) 32 | 33 | 34 | @bot.tree.command(description="Shows the help menu", nsfw=True) 35 | async def help(ctx: discord.Interaction): 36 | await ctx.response.defer() 37 | ret = "```\n" 38 | ret += "rule34 [tags]\n" 39 | ret += "Get image/video from Rule34 (EXTREME CONTENT AHEAD!!!).\n" 40 | ret += "gelbooru [tags]\n" 41 | ret += "Get image/video from Gelbooru\n" 42 | ret += "yandere [tags]\n" 43 | ret += "Get image from Yande.re\n" 44 | ret += "konachan [tags]\n" 45 | ret += "Get image from Konachan\n" 46 | ret += "danbooru [tags]\n" 47 | ret += "Get image/video from Danbooru\n" 48 | ret += "nekosfun [tag]\n" 49 | ret += "Get images/gifs from NekosLife\n" 50 | ret += "boobchi\n" 51 | ret += "Get a random ecchi image of Hitori Gotoh\n\n" 52 | ret += "TAG FORMATTING:\n" 53 | ret += "Using `,` after adding another tag turns to `+` and leaving a space turn some tags to `_`." 54 | ret += "```" 55 | await ctx.followup.send(ret) 56 | 57 | @bot.tree.command(description="Get hentai from Rule34 (EXTREME!!!)", nsfw=True) 58 | @app_commands.describe(tag="Add a tag") 59 | async def rule34(ctx: discord.Interaction, tag: Optional[str] = None) -> None: 60 | await ctx.response.defer() 61 | ret = await Hentai().get_nsfw_image(NsfwApis.Rule34Api, tag) 62 | source = ret["source"] 63 | owner = ret["owner"] 64 | score = ret["score"] 65 | image = ret["file_url"] 66 | if image.endswith(".mp4"): 67 | await ctx.followup.send(image) 68 | else: 69 | embed = discord.Embed(title="Created by {}".format(owner)) 70 | embed.color=discord.Color.random() 71 | if source != "": 72 | embed.add_field( 73 | name="Source", value="[Click here]({})".format(source), inline=True 74 | ) 75 | embed.add_field(name="Score", value=score, inline=True) 76 | embed.set_image(url=image) 77 | embed.set_footer(text="Fetched from Rule34") 78 | await ctx.followup.send(embed=embed) 79 | 80 | @bot.tree.command(description="Get hentai from Gelbooru", nsfw=True) 81 | @app_commands.describe(tag="Add a tag") 82 | async def gelbooru(ctx: discord.Interaction, tag: Optional[str] = None) -> None: 83 | await ctx.response.defer() 84 | ret = await Hentai().get_nsfw_image(NsfwApis.GelbooruApi, tag) 85 | source = ret["source"] 86 | owner = ret["owner"] 87 | score = ret["score"] 88 | image = ret["file_url"] 89 | created_at = ret["created_at"] 90 | if image.endswith(".mp4"): 91 | await ctx.followup.send(image) 92 | else: 93 | embed = discord.Embed(title="Created by {}".format(owner)) 94 | embed.color=discord.Color.random() 95 | if source != "": 96 | embed.add_field( 97 | name="Source", value="[Click here]({})".format(source), inline=True 98 | ) 99 | embed.add_field(name="Score", value=score, inline=True) 100 | embed.set_image(url=image) 101 | embed.set_footer(text="Fetched from Gelbooru\nCreated at {}".format(created_at)) 102 | await ctx.followup.send(embed=embed) 103 | 104 | 105 | @bot.tree.command(description="Get hentai from Yandere", nsfw=True) 106 | @app_commands.describe(tag="Add a tag") 107 | async def yandere(ctx: discord.Interaction, tag: Optional[str] = None) -> None: 108 | await ctx.response.defer() 109 | ret = await Hentai().get_nsfw_image(NsfwApis.YandereApi, tag) 110 | created_at = datetime.fromtimestamp(int(ret["created_at"])) 111 | file = ret["file_url"] 112 | author = ret["author"] 113 | source = ret["source"] 114 | score = ret["score"] 115 | embed = discord.Embed(title="Created by {}".format(author)) 116 | embed.color=discord.Color.random() 117 | if source != "": 118 | embed.add_field( 119 | name="Source", value="[Click here]({})".format(source), inline=True 120 | ) 121 | embed.add_field(name="Score", value=score, inline=True) 122 | embed.set_image(url=file) 123 | embed.set_footer(text="Fetched from Yande.re\nCreated at {}".format(created_at)) 124 | await ctx.followup.send(embed=embed) 125 | 126 | 127 | @bot.tree.command(description="Get hentai from Konachan", nsfw=True) 128 | @app_commands.describe(tag="Add a tag") 129 | async def konachan(ctx: discord.Interaction, tag: Optional[str] = None) -> None: 130 | await ctx.response.defer() 131 | 132 | ret = await Hentai().get_nsfw_image(NsfwApis.KonachanApi, tag) 133 | 134 | created_at = datetime.fromtimestamp(int(ret["created_at"])) 135 | file = ret["file_url"] 136 | author = ret["author"] 137 | source = ret["source"] 138 | score = ret["score"] 139 | embed = discord.Embed(title="Created by {}".format(author)) 140 | embed.color=discord.Color.random() 141 | if source != "": 142 | embed.add_field( 143 | name="Source", value="[Click here]({})".format(source), inline=True 144 | ) 145 | embed.add_field(name="Score", value=score, inline=True) 146 | embed.set_image(url=file) 147 | embed.set_footer(text="Fetched from Konachan\nCreated at {}".format(created_at)) 148 | await ctx.followup.send(embed=embed) 149 | 150 | 151 | @bot.tree.command(description="Get hentai from Danbooru", nsfw=True) 152 | @app_commands.describe(tag="Add a tag") 153 | async def danbooru(ctx: discord.Interaction, tag: Optional[str] = None) -> None: 154 | await ctx.response.defer() 155 | ret = await Hentai().get_nsfw_image(NsfwApis.DanbooruApi, tag) 156 | created_at = ret["created_at"] 157 | file = ret["file_url"] 158 | ret["source"] 159 | author = ret["tag_string_artist"] 160 | source = ret["source"] 161 | score = ret["score"] 162 | embed = discord.Embed(title="Created by {}".format(author)) 163 | embed.color=discord.Color.random() 164 | if source != "": 165 | embed.add_field( 166 | name="Source", value="[Click here]({})".format(source), inline=True 167 | ) 168 | embed.add_field(name="Score", value=score, inline=True) 169 | embed.set_image(url=file) 170 | embed.set_footer(text="Fetched from Danbooru\nCreated at {}".format(created_at)) 171 | await ctx.followup.send(embed=embed) 172 | 173 | 174 | @bot.tree.command(description="Get an image/gif from NekosLife", nsfw=True) 175 | @app_commands.describe(tag="Which tag?") 176 | async def nekosfun(ctx: discord.Interaction, tag: Optional[NekosFunTags]) -> None: 177 | await ctx.response.defer() 178 | tag = tag if tag else random.choice(list(NekosFunTags)) 179 | image = Hentai.nekofun(tag.value) 180 | 181 | if image == False: 182 | await ctx.followup.send("An error has occurred!") 183 | else: 184 | embed = discord.Embed() 185 | embed.color=discord.Color.random() 186 | embed.set_image(url=image) 187 | embed.set_footer(text="Fetched from Nekos.Fun") 188 | await ctx.followup.send(embed=embed) 189 | 190 | @bot.tree.command(description="Get a random ecchi image of Hitori Gotoh") 191 | async def boobchi(ctx:discord.Interaction): 192 | await ctx.response.defer() 193 | image, source = Hentai.boobchi() 194 | 195 | if image == False: 196 | await ctx.followup.send("An error has occurred!") 197 | else: 198 | embed = discord.Embed() 199 | embed.color=discord.Color.random() 200 | embed.description=f"[Source]({source})" 201 | embed.set_image(url=image) 202 | embed.set_footer(text="Fetched from Bocchi-API") 203 | await ctx.followup.send(embed=embed) 204 | 205 | 206 | @bot.tree.error 207 | async def on_app_command_error( 208 | ctx: discord.Interaction, error: app_commands.errors.AppCommandError 209 | ): 210 | if isinstance(error, app_commands.errors.CommandInvokeError) and isinstance(error.original, (IndexError, TypeError, KeyError)): 211 | embed = discord.Embed( 212 | title="Hentai Failed", 213 | description="Hentai couldn't be sent in this channel", 214 | color=0xFF0000, 215 | ).add_field(name="Reason", value="Tag not found or hentai could not be found") 216 | await ctx.followup.send(embed=embed) 217 | 218 | 219 | bot.run(token) 220 | -------------------------------------------------------------------------------- /requirements.py: -------------------------------------------------------------------------------- 1 | import os 2 | import platform 3 | 4 | pip_install = 'pip install -U pip' 5 | packages = 'pip install -U discord requests aiohttp python-dotenv' 6 | 7 | if platform.system == 'Windows': # if OS is Windows 8 | os.system('python.exe -m' + pip_install) 9 | 10 | elif platform.system=='Linux': # if OS is Linux 11 | os.system('python3 -m' + pip_install) 12 | 13 | os.system(packages) 14 | --------------------------------------------------------------------------------