Release v2

This commit is contained in:
2026-07-16 02:12:29 -03:00
parent 6f98163632
commit 76cbd799ce
47 changed files with 2409 additions and 2810 deletions
View File
+39
View File
@@ -0,0 +1,39 @@
import orjson
from redis.asyncio import Redis
from nyekiapi.lib import settings
redis = Redis.from_url(settings.REDIS_URL)
async def get(key: str) -> str | None:
"""Retrieve a value from Redis by key.
Args:
key (str): The key to retrieve the value for.
Returns:
str | None: The value associated with the key, or None if the key does not exist.
"""
raw = await redis.get(key)
if raw:
return orjson.loads(raw)
else:
return None
async def set(key: str, value: str, expires_in: int | None = None) -> None:
"""Set a value in Redis with an optional expiration time.
Args:
key (str): The key to set the value for.
value (str): The value to set.
expires_in (int | None, optional): The expiration time in seconds. Defaults to None.
"""
await redis.set(key, orjson.dumps(value), ex=expires_in)
+18
View File
@@ -0,0 +1,18 @@
from dataclasses import dataclass
@dataclass
class Profile:
id: str
url: str
name: str
username: str
avatar_url: str
banner_url: str | None
@classmethod
def from_json(cls, json: dict) -> "Profile":
return cls(**json)
def into_json(self) -> dict:
return self.__dict__
+69
View File
@@ -0,0 +1,69 @@
from typing import TypedDict, NotRequired
from nyekiapi.lib import settings, cache
from nyekiapi.lib.requests import httpx
from nyekiapi.lib.integrations import Profile
CACHE_BY_ID_KEY = "discord:profile:id:%s"
class DiscordProfile(TypedDict):
id: str
username: str
global_name: str | None
discriminator: str
avatar: str | None
banner: NotRequired[str | None]
def _get_username(user: DiscordProfile) -> str:
if user["discriminator"] == "0":
return user["username"]
return f"{user['username']}#{user['discriminator']}"
def _get_avatar_url(user: DiscordProfile) -> str:
if user["avatar"] is None:
if user["discriminator"] == "0":
return f"https://cdn.discordapp.com/embed/avatars/{(int(user['id']) >> 22) % 6}.png?size=256"
else:
return f"https://cdn.discordapp.com/embed/avatars/{int(user['discriminator']) % 6}.png?size=256"
else:
return f"https://cdn.discordapp.com/avatars/{user['id']}/{user['avatar']}.png?size=256"
def _get_banner_url(user: DiscordProfile) -> str | None:
if "banner" not in user or user["banner"] is None:
return None
return f"https://cdn.discordapp.com/banners/{user['id']}/{user['banner']}.png?size=2048"
async def get_profile_by_id(id: str) -> Profile:
profile = await cache.get(CACHE_BY_ID_KEY % id)
if profile is not None:
return Profile.from_json(profile)
response = await httpx.get(
f"https://discord.com/api/v10/users/{id}",
headers={"Authorization": f"Bot {settings.DISCORD_TOKEN}"},
)
response.raise_for_status()
data: DiscordProfile = response.json()
profile = Profile(
id=data["id"],
url=f"https://discord.com/users/{data['id']}",
name=data.get("global_name") or _get_username(data),
username=_get_username(data),
avatar_url=_get_avatar_url(data),
banner_url=_get_banner_url(data),
)
await cache.set(CACHE_BY_ID_KEY % id, profile, expires_in=10 * 60)
return profile
+82
View File
@@ -0,0 +1,82 @@
from typing import TypedDict
from urllib.parse import quote
from nyekiapi.lib import settings, project, cache
from nyekiapi.lib.requests import httpx
from nyekiapi.lib.integrations import Profile
CACHE_BY_ID_KEY = "github:profile:id:%s"
CACHE_BY_NAME_KEY = "github:profile:name:%s"
class GithubProfile(TypedDict):
login: str
name: str | None
avatar_url: str
html_url: str
async def get_profile_by_id(id: str) -> Profile:
profile = await cache.get(CACHE_BY_ID_KEY % id)
if profile is not None:
return Profile.from_json(profile)
response = await httpx.get(
f"https://api.github.com/user/{quote(id)}",
headers={
"Authorization": f"Bearer {settings.GITHUB_TOKEN}",
"User-Agent": f"NyekiAPI/{project.PROJECT_VERSION}",
},
)
response.raise_for_status()
data: GithubProfile = response.json()
profile = Profile(
id=id,
url=data["html_url"],
name=data.get("name") or data["login"],
username=data["login"],
avatar_url=data["avatar_url"],
banner_url=None,
)
await cache.set(CACHE_BY_ID_KEY % profile.id, profile, expires_in=10 * 60)
await cache.set(CACHE_BY_NAME_KEY % profile.username, profile, expires_in=10 * 60)
return profile
async def get_profile_by_name(username: str) -> Profile:
profile = await cache.get(CACHE_BY_NAME_KEY % username)
if profile is not None:
return Profile.from_json(profile)
response = await httpx.get(
f"https://api.github.com/users/{quote(username)}",
headers={
"Authorization": f"Bearer {settings.GITHUB_TOKEN}",
"User-Agent": f"NyekiAPI/{project.PROJECT_VERSION}",
},
)
response.raise_for_status()
data: GithubProfile = response.json()
profile = Profile(
id=str(data["id"]),
url=data["html_url"],
name=data.get("name") or data["login"],
username=data["login"],
avatar_url=data["avatar_url"],
banner_url=None,
)
await cache.set(CACHE_BY_ID_KEY % profile.id, profile, expires_in=10 * 60)
await cache.set(CACHE_BY_NAME_KEY % profile.username, profile, expires_in=10 * 60)
return profile
+11
View File
@@ -0,0 +1,11 @@
import tomllib
file = open("pyproject.toml", "rb")
data = tomllib.load(file)
file.close()
PROJECT_NAME = data["project"]["name"]
PROJECT_VERSION = data["project"]["version"]
PROJECT_DESCRIPTION = data["project"]["description"]
+4
View File
@@ -0,0 +1,4 @@
from httpx import AsyncClient
httpx = AsyncClient()
+32
View File
@@ -0,0 +1,32 @@
import os
from dotenv import load_dotenv
load_dotenv()
DEBUG = os.getenv("NYEKI_DEBUG", "False").lower() in ("true", "1", "t")
DATABASE = {
"connections": {"default": os.getenv("NYEKI_DATABASE_URL", "sqlite://db.sqlite3")},
"apps": {
"models": {
"models": [
"nyekiapi.db.models.example",
],
"default_connection": "default",
"migrations": "nyekiapi.db.migrations",
}
},
"use_tz": True,
"timezone": "UTC",
}
DISCORD_TOKEN = os.getenv("NYEKI_DISCORD_TOKEN")
GITHUB_TOKEN = os.getenv("NYEKI_GITHUB_TOKEN")
REDIS_URL = os.getenv("NYEKI_REDIS_URL", "redis://localhost:6379/0")