Skip to content

Commit

Permalink
Addes support for the Nekos.best API
Browse files Browse the repository at this point in the history
  • Loading branch information
Nekidev committed Sep 24, 2022
1 parent 1b38df4 commit f971286
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 5 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ These are the currently supported and planned to add support for APIs:
| Nekos.life | [Documentation](https://github.com/Nekos-life/nekos.py) ||
| NekoBot | [Documentation](https://docs.nekobot.xyz/) ||
| Neko-Love | [Documentation](https://docs.neko-love.xyz/) ||
| Nekos.best | [Documentation](https://docs.nekos.best/) | |
| Nekos.best | [Documentation](https://docs.nekos.best/) | |
| Nekos.moe | [Documentation](https://docs.nekos.moe/) ||
| Shikimori | [Documentation](https://shikimori.one/api/doc) ||
| MangaDex | [Documentation](https://api.mangadex.org/docs.html) ||
Expand Down Expand Up @@ -109,6 +109,16 @@ You know what you want to do, but have no idea of what API will work for you? Th
- SFW and NSFW images
- Get any amount of random images
- Completely free
- Nekos.best
- Lots of different neko images
- Get random images from +35 different categories
- Get many random images with a single API call
- Search for images by category, format and more
- Fully SFW
- 99.9% uptime
- Fast response times
- Get all image's source
- Completely free


#### Facts
Expand Down
4 changes: 2 additions & 2 deletions anime_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
("Nekos.life", None, "https://github.com/Nekos-life/nekos.py", False),
("NekoBot", apis.NekoBotAPI, "https://docs.nekobot.xyz/", True),
("Neko-love", apis.NekoLoveAPI, "https://docs.neko-love.xyz/", True),
("Nekos.moe", None, "https://docs.nekos.moe/", False),
("Nekos.best", None, "https://docs.nekos.best/", False),
("Nekos.moe", apis.NekosMoeAPI, "https://docs.nekos.moe/", True),
("Nekos.best", apis.NekosBest, "https://docs.nekos.best/", True),
("Shikimori", None, "https://shikimori.one/api/doc", False),
("Mangadex", None, "https://api.mangadex.org/docs.html", False),
("Danbooru", None, "https://danbooru.donmai.us/wiki_pages/help:api", False),
Expand Down
1 change: 1 addition & 0 deletions anime_api/apis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
from .nekobot import NekoBotAPI
from .neko_love import NekoLoveAPI
from .nekos_moe import NekosMoeAPI
from .nekos_best import NekosBest
102 changes: 102 additions & 0 deletions anime_api/apis/nekos_best/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
Base module for the nekos.best API. Documentation can be found at https://docs.nekos.best
"""
import typing
import requests

from anime_api import exceptions
from anime_api.apis.nekos_best.objects import Image, Artist, Anime
from anime_api.apis.nekos_best.types import ImageCategory, ImageType


class NekosBest:
"""
Docs: https://docs.nekos.best/
"""

endpoint = "https://nekos.best/api/v2"

def get_random_images(
self, image_type: ImageCategory, amount: int = 1
) -> typing.List[Image]:
"""
Returns a list of random images
"""

if amount < 1 or amount > 20:
raise ValueError("Amount must be between 1 and 20")

response = requests.get(
self.endpoint + "/" + image_type.value, params={"amount": amount}
)

if response.status_code != 200:
raise exceptions.ServerError(status_code=response.status_code)

return [
Image(
url=image["url"],
artist=Artist(
url=image["artist_href"],
name=image["artist_name"],
)
if "artist_href" in image
else None,
anime=Anime(
title=image["anime_name"],
)
if "anime_name" in image
else None,
source_url=image["source_url"] if "source_url" in image else None,
)
for image in response.json()["results"]
]

def search_images(
self,
query: str,
image_category: typing.Optional[ImageCategory] = None,
image_type: typing.Optional[ImageType] = None,
amount: int = 1,
) -> typing.List[Image]:
"""
Returns a list of images that match the query
"""

if amount < 1 or amount > 20:
raise ValueError("Amount must be between 1 and 20")

params = {
"query": query,
"amount": amount,
}

if image_type:
params["type"] = image_type.value

if image_category:
params["category"] = image_category.value

response = requests.get(self.endpoint + "/search", params=params)

if response.status_code != 200:
raise exceptions.ServerError(status_code=response.status_code)

return [
Image(
url=image["url"],
artist=Artist(
url=image["artist_href"],
name=image["artist_name"],
)
if "artist_href" in image
else None,
anime=Anime(
title=image["anime_name"],
)
if "anime_name" in image
else None,
source_url=image["source_url"] if "source_url" in image else None,
)
for image in response.json()["results"]
]
34 changes: 34 additions & 0 deletions anime_api/apis/nekos_best/objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from dataclasses import dataclass

import typing


@dataclass
class Artist:
"""
Object representation of an image's artist
"""

url: typing.Optional[str] = None
name: typing.Optional[str] = None


@dataclass
class Anime:
"""
Object representation of an anime
"""

title: str


@dataclass
class Image:
"""
Object representation of an image
"""

url: str
artist: typing.Optional[Artist] = None
anime: typing.Optional[Anime] = None
source_url: typing.Optional[str] = None
54 changes: 54 additions & 0 deletions anime_api/apis/nekos_best/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from enum import Enum


class ImageCategory(Enum):
"""
Image's category
"""

HIGHFIVE = "highfive"
HAPPY = "happy"
SLEEP = "sleep"
HANDHOLD = "handhold"
LAUGH = "laugh"
BITE = "bite"
POKE = "poke"
TICKLE = "tickle"
KISS = "kiss"
WAVE = "wave"
THUMBSUP = "thumbsup"
STARE = "stare"
CUDDLE = "cuddle"
SMILE = "smile"
BAKA = "baka"
BLUSH = "blush"
THINK = "think"
POUT = "pout"
FACEPALM = "facepalm"
WINK = "wink"
SHOOT = "shoot"
SMUG = "smug"
CRY = "cry"
PAT = "pat"
PUNCH = "punch"
DANCE = "dance"
FEED = "feed"
SHRUG = "shrug"
BORED = "bored"
KICK = "kick"
HUG = "hug"
YEET = "yeet"
SLAP = "slap"
NEKO = "neko"
HUSBANDO = "husbando"
KITSUNE = "kitsune"
WAIFU = "waifu"


class ImageType(Enum):
"""
Image format
"""

IMAGE = 1
GIF = 2
94 changes: 93 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All API wrappers can be imported from `anime_api.apis`. For example, the Anime F
- [NekoBot API](#nekobot-api)
- [Neko-Love API](#neko-love-api)
- [Nekos.moe API](#nekosmoe-api)
- [Nekos.best](#nekosbest)

## Installation

Expand Down Expand Up @@ -1433,4 +1434,95 @@ The `PendingImage` class has the following attributes:

- `image`: (`Image`) The image that was uploaded.
- `image_url`: (`str`) The image URL. This is the same as `image.url`.
- `post_url`: (`str`) The URL to the post on Nekos.moe.
- `post_url`: (`str`) The URL to the post on Nekos.moe.

## Nekos.best

The Nekos.best API is an API to get a variety of random (or not so random) anime images. The `NekosBest` class can be imported from `anime_api.apis`.

```python3
from anime_api.apis import NekosBest

api = NekosBest()
```

### `get_random_image(category: ImageCategory)`

The `get_random_image()` method returns a `anime_api.apis.nekos_best.objects.Image` object.

```python3
from anime_api.apis import NekosBest

api = NekosBest()

image = api.get_random_image(category=ImageCategory.ANIME)
```

The `anime_api.apis.nekos_best.types.ImageCategory` class has the following properties:

- `HIGHFIVE`
- `HAPPY`
- `SLEEP`
- `HANDHOLD`
- `LAUGH`
- `BITE`
- `POKE`
- `TICKLE`
- `KISS`
- `WAVE`
- `THUMBSUP`
- `STARE`
- `CUDDLE`
- `SMILE`
- `BAKA`
- `BLUSH`
- `THINK`
- `POUT`
- `FACEPALM`
- `WINK`
- `SHOOT`
- `SMUG`
- `PAT`
- `PUNCH`
- `DANCE`
- `FEED`
- `SHRUG`
- `BORED`
- `KICK`
- `HUG`
- `YEET`
- `SLAP`
- `NEKO`
- `HUSBANDO`
- `KITSUNE`
- `WAIFU`

### The `Image` class

The `Image` class has the following attributes:

- `url`: (`str`) The image URL.
- `artist`: (`Artist | None`)
- `artist.name`: (`str`) The artist's name.
- `artist.url`: (`str`) The artist's URL.
- `anime`: (`Anime | None`)
- `anime.title`: (`str`) The anime's title.
- `source_url`: (`str | None`) The source URL of the image.

### `search_images(query: str, image_category: ImageCategory | None = None, image_type: ImageType | None = None, amount: int = 1)`

The `search_images()` method returns a list of `Image` object.

```python3
from anime_api.apis import NekosBest
from anime_api.apis.nekos_best.types import ImageCategory, ImageType

api = NekosBest()

images = api.search_images(query="cute neko", image_category=ImageCategory.NEKO, image_type=ImageType.GIF, amount=5)
```

The `anime_api.apis.nekos_best.types.ImageType` class has the following properties:

- `IMAGE`: Return only static images
- `GIF`: Return only GIFs
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "anime-api"
version = "0.12.2"
version = "0.13.0"
description = "A collection of wrappers for anime-related APIs"
authors = ["Neki <84998222+Nekidev@users.noreply.github.com>"]
readme = "README.md"
Expand Down
28 changes: 28 additions & 0 deletions tests/nekos_best.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
Run tests for the NekosBest class
"""
from anime_api.apis import NekosBest
from anime_api.apis.nekos_best.objects import Image
from anime_api.apis.nekos_best.types import ImageCategory, ImageType


def test_get_random_images():
"""
Tests the get_random_images method
"""
api = NekosBest()
images = api.get_random_images(ImageCategory.NEKO)
assert isinstance(images, list)
assert len(images) > 0
assert isinstance(images[0], Image)


def test_search_images():
"""
Tests the search_images method
"""
api = NekosBest()
images = api.search_images("neko", ImageCategory.NEKO, ImageType.IMAGE, 3)
for image in images:
assert isinstance(image, Image)
assert not image.url.endswith(".gif")

0 comments on commit f971286

Please sign in to comment.