feat(anilist): add account intergration

This commit is contained in:
Benex254
2024-08-05 09:47:04 +03:00
parent 7b0ba6a8c8
commit 1433e04f73
7 changed files with 254 additions and 9 deletions

View File

@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
class UserData:
user_data = {"watch_history": {}, "animelist": []}
user_data = {"watch_history": {}, "animelist": [], "user": {}}
def __init__(self):
try:
@@ -23,6 +23,10 @@ class UserData:
self.user_data["watch_history"] = watch_history
self._update_user_data()
def update_user_info(self, user: dict):
self.user_data["user"] = user
self._update_user_data()
def update_animelist(self, anime_list: list):
self.user_data["animelist"] = list(set(anime_list))
self._update_user_data()

View File

@@ -3,6 +3,7 @@ import click
from ...interfaces.anilist_interfaces import anilist as anilist_interface
from ...utils.tools import QueryDict
from .favourites import favourites
from .login import loggin
from .popular import popular
from .random_anime import random_anime
from .recent import recent
@@ -20,6 +21,7 @@ commands = {
"popular": popular,
"favourites": favourites,
"random": random_anime,
"loggin": loggin,
}

View File

@@ -0,0 +1,35 @@
import webbrowser
import click
from rich import print
from rich.prompt import Prompt
from ....anilist import AniList
from ...config import Config
from ...utils.tools import exit_app
@click.command(help="Login to your anilist account")
@click.option("--status", "-s", help="Whether you are logged in or not", is_flag=True)
@click.pass_obj
def loggin(config: Config, status):
if status:
is_logged_in = True if config.user else False
message = (
"You are logged in :happy:" if is_logged_in else "You arent logged in :sad:"
)
print(message)
exit_app()
if config.user:
print("Already logged in :confused:")
exit_app()
# ---- new loggin -----
print("A browser session will be opened")
webbrowser.open(config.fastanime_anilist_app_login_url)
print("Please paste the token provided here")
token = Prompt.ask("Enter token")
user = AniList.login_user(token)
config.update_user(user)
print("Successfully saved credentials")
print(user)
exit_app()

View File

@@ -11,6 +11,9 @@ from ..Utility.user_data_helper import user_data_helper
class Config(object):
anime_list: list
watch_history: dict
fastanime_anilist_app_login_url = (
"https://anilist.co/api/v2/oauth/authorize?client_id=20148&response_type=token"
)
def __init__(self) -> None:
self.load_config()
@@ -59,9 +62,14 @@ class Config(object):
# ---- setup user data ------
self.watch_history: dict = user_data_helper.user_data.get("watch_history", {})
self.anime_list: list = user_data_helper.user_data.get("animelist", [])
self.user: dict = user_data_helper.user_data.get("user", {})
self.anime_provider = AnimeProvider(self.provider)
def update_user(self, user):
self.user = user
user_data_helper.update_user_info(user)
def update_watch_history(self, anime_id: int, episode: str | None):
self.watch_history.update({str(anime_id): episode})
user_data_helper.update_watch_history(self.watch_history)

View File

@@ -17,6 +17,13 @@ class AnilistImage(TypedDict):
large: str
class AnilistUser(TypedDict):
id: int
name: str
bannerImage: str | None
avatar: AnilistImage
class AnilistMediaTrailer(TypedDict):
id: str
site: str
@@ -49,11 +56,6 @@ class AnilistMediaNextAiringEpisode(TypedDict):
episode: int
class AnilistUser(TypedDict):
name: str
avatar: AnilistImage
class AnilistReview(TypedDict):
summary: str
user: AnilistUser

View File

@@ -2,14 +2,18 @@
This is the core module availing all the abstractions of the anilist api
"""
from typing import Literal
import requests
from .anilist_data_schema import AnilistDataSchema
from .anilist_data_schema import AnilistDataSchema, AnilistUser
from .queries_graphql import (
airing_schedule_query,
anime_characters_query,
anime_query,
anime_relations_query,
get_logged_in_user_query,
media_list_query,
most_favourite_query,
most_popular_query,
most_recently_updated_query,
@@ -21,6 +25,7 @@ from .queries_graphql import (
)
# from kivy.network.urlrequest import UrlRequestRequests
ANILIST_ENDPOINT = "https://graphql.anilist.co"
class AniListApi:
@@ -28,6 +33,76 @@ class AniListApi:
This class provides an abstraction for the anilist api
"""
def login_user(self, token: str):
self.token = token
self.headers = {"Authorization": f"Bearer {self.token}"}
user = self.get_logged_in_user()
if not user:
return
if not user[0]:
return
user_info: AnilistUser = user[1]["data"]["Viewer"] # pyright:ignore
self.user_id = user_info["id"] # pyright:ignore
return user_info
def update_login_info(self, user: AnilistUser, token: str):
self.token = token
self.headers = {"Authorization": f"Bearer {self.token}"}
self.user_id = user["id"]
def get_logged_in_user(self):
if not self.headers:
return
return self._make_authenticated_request(get_logged_in_user_query)
def get_anime_list(
self,
status: Literal[
"CURRENT", "PLANNING", "COMPLETED", "DROPPED", "PAUSED", "REPEATING"
],
):
variables = {"status": status, "id": self.user_id}
return self._make_authenticated_request(media_list_query, variables)
def _make_authenticated_request(self, query: str, variables: dict = {}):
"""
The core abstraction for getting authenticated data from the anilist api
Parameters:
----------
query:str
a valid anilist graphql query
variables:dict
variables to pass to the anilist api
"""
# req=UrlRequestRequests(url, self.got_data,)
try:
# TODO: check if data is as expected
response = requests.post(
ANILIST_ENDPOINT,
json={"query": query, "variables": variables},
timeout=10,
headers=self.headers,
)
anilist_data = response.json()
return (True, anilist_data)
except requests.exceptions.Timeout:
return (
False,
{
"Error": "Timeout Exceeded for connection there might be a problem with your internet or anilist is down."
},
) # type: ignore
except requests.exceptions.ConnectionError:
return (
False,
{
"Error": "There might be a problem with your internet or anilist is down."
},
) # type: ignore
except Exception as e:
return (False, {"Error": f"{e}"}) # type: ignore
def get_data(
self, query: str, variables: dict = {}
) -> tuple[bool, AnilistDataSchema]:
@@ -41,12 +116,13 @@ class AniListApi:
variables:dict
variables to pass to the anilist api
"""
url = "https://graphql.anilist.co"
# req=UrlRequestRequests(url, self.got_data,)
try:
# TODO: check if data is as expected
response = requests.post(
url, json={"query": query, "variables": variables}, timeout=10
ANILIST_ENDPOINT,
json={"query": query, "variables": variables},
timeout=10,
)
anilist_data: AnilistDataSchema = response.json()
return (True, anilist_data)

View File

@@ -3,6 +3,124 @@ This module contains all the preset queries for the sake of neatness and convini
Mostly for internal usage
"""
get_logged_in_user_query = """
query{
Viewer{
id
name
bannerImage
avatar {
large
medium
}
}
}
"""
media_list_mutation = """
mutation($mediaId:Int,$id:Int,$scoreRaw:Int,$repeat:Int,$progress:Int){
SaveMediaListEntry(mediaId:$mediaId,id:$id,scoreRaw:$scoreRaw,progress:$progress,repeat:$repeat){
id
status
mediaId
score
progress
repeat
startedAt {
year
month
day
}
completedAt {
year
month
day
}
}
}
"""
media_list_query = """
query ($userId: Int, $status: MediaListStatus) {
Page {
pageInfo {
currentPage
total
}
mediaList(userId: $userId, status: $status) {
mediaId
media {
id
title {
romaji
english
}
coverImage {
medium
large
}
trailer {
site
id
}
popularity
favourites
averageScore
episodes
genres
studios {
nodes {
name
isAnimationStudio
}
}
tags {
name
}
startDate {
year
month
day
}
endDate {
year
month
day
}
status
description
nextAiringEpisode {
timeUntilAiring
airingAt
episode
}
}
status
progress
score
repeat
notes
startedAt {
year
month
day
}
completedAt {
year
month
day
}
createdAt
}
}
}
"""
optional_variables = "\
$page:Int,\
$sort:[MediaSort],\