Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
SageTendo committed Jan 22, 2024
2 parents 520443d + 6b52770 commit e0b4de0
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 133 deletions.
202 changes: 113 additions & 89 deletions Pipfile.lock

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion app/api/mal.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ def get_token(self, authorization_code: str):
'refresh_token': resp_json['refresh_token']
}

@staticmethod
def get_user_details(token: str):
"""
Get the user's details from MyAnimeList
:param token: The user's access token
:return: JSON response
"""
if token is None:
raise Exception("Auth Token Must Be Provided")

headers = kwargs_to_dict(Authorization=f'Bearer {token}')
resp = requests.get(f'{BASE_URL}/users/@me', headers=headers)
resp.raise_for_status()
return resp.json()

# noinspection DuplicatedCode
@staticmethod
def get_anime_list(token: str, query: str, **kwargs):
Expand Down Expand Up @@ -132,7 +147,7 @@ def get_anime_details(token: str, anime_id: str, **kwargs):
raise Exception("Auth Token Must Be Provided")

if anime_id is None:
raise Exception("A Valid Query Must Be Provided")
raise Exception("A Valid Anime ID Must Be Provided")

headers = kwargs_to_dict(Authorization=f'Bearer {token}')
query_params = to_query_string(kwargs)
Expand Down
1 change: 1 addition & 0 deletions app/db/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
client = MongoClient(Config.MONGO_URI)
db = client.get_database(Config.MONGO_DB)
anime_map_collection = db.get_collection(Config.MONGO_ANIME_MAP)
UID_map_collection = db.get_collection(Config.MONGO_UID_MAP)
18 changes: 15 additions & 3 deletions app/routes/auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask import Blueprint, request, render_template
from flask import Blueprint, request, render_template, session
from werkzeug.utils import redirect

from app.db.db import UID_map_collection
from app.routes import mal_client
from config import Config

Expand All @@ -26,6 +27,17 @@ def redirect_url():
auth_data = mal_client.get_token(code)
access_token = auth_data['access_token']

manifest_url = f'{Config.PROTOCOL}://{Config.REDIRECT_URL}/{access_token}/manifest.json'
manifest_magnet = f'stremio://{Config.REDIRECT_URL}/{access_token}/manifest.json'
# Get the user's username
user_details = mal_client.get_user_details(access_token)
user_id = str(user_details['id'])

# Add the user to the database
user = UID_map_collection.find_one({'uid': user_id})
if user:
UID_map_collection.update_one(user, {'$set': {'access_token': access_token}})
else:
UID_map_collection.insert_one({'uid': user_id, 'access_token': access_token})

manifest_url = f'{Config.PROTOCOL}://{Config.REDIRECT_URL}/{user_id}/manifest.json'
manifest_magnet = f'stremio://{Config.REDIRECT_URL}/{user_id}/manifest.json'
return render_template('index.html', manifest_url=manifest_url, manifest_magnet=manifest_magnet)
20 changes: 12 additions & 8 deletions app/routes/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@

from . import mal_client
from .manifest import MANIFEST
from .utils import respond_with, mal_to_meta
from .utils import respond_with, mal_to_meta, get_token

catalog_bp = Blueprint('catalog', __name__)


@catalog_bp.route('/<token>/catalog/<catalog_type>/<catalog_id>.json')
@catalog_bp.route('/<token>/catalog/<catalog_type>/<catalog_id>/skip=<offset>.json')
def addon_catalog(token: str, catalog_type: str, catalog_id: str, offset: str = None):
@catalog_bp.route('/<user_id>/catalog/<catalog_type>/<catalog_id>.json')
@catalog_bp.route('/<user_id>/catalog/<catalog_type>/<catalog_id>/skip=<offset>.json')
def addon_catalog(user_id: str, catalog_type: str, catalog_id: str, offset: str = None):
"""
Provides a list of anime from MyAnimeList
:param token: The user's API token for MyAnimeList
:param user_id: The user's MyAnimeList ID
:param catalog_type: The type of catalog to return
:param catalog_id: The ID of the catalog to return, MAL divides a user's anime list into different categories
(e.g. plan to watch, watching, completed, on hold, dropped)
Expand All @@ -32,6 +32,8 @@ def addon_catalog(token: str, catalog_type: str, catalog_id: str, offset: str =
if not catalog_exists:
abort(404)

token = get_token(user_id)

field_params = 'media_type genres mean start_date end_date synopsis' # Additional fields to return
response_data = mal_client.get_user_anime_list(token, status=catalog_id, offset=offset, fields=field_params)
response_data = response_data['data'] # Get array of node objects
Expand All @@ -46,11 +48,11 @@ def addon_catalog(token: str, catalog_type: str, catalog_id: str, offset: str =
return respond_with({'metas': meta_previews})


@catalog_bp.route('/<token>/catalog/<catalog_type>/<catalog_id>/search=<search_query>.json')
def search_metas(token: str, catalog_type: str, catalog_id: str, search_query: str):
@catalog_bp.route('/<user_id>/catalog/<catalog_type>/<catalog_id>/search=<search_query>.json')
def search_metas(user_id: str, catalog_type: str, catalog_id: str, search_query: str):
"""
Provides a list of anime from MyAnimeList based on a search query
:param token: The user's API token for MyAnimeList
:param user_id: The user's MyAnimeList ID
:param catalog_type: The type of catalog to return
:param catalog_id: The ID of the catalog to return, MAL divides a user's anime list into different categories
(e.g. plan to watch, watching, completed, on hold, dropped)
Expand All @@ -69,6 +71,8 @@ def search_metas(token: str, catalog_type: str, catalog_id: str, search_query: s
if not catalog_exists:
abort(404)

token = get_token(user_id)

field_params = 'media_type alternative_titles' # Additional fields to return
response = mal_client.get_anime_list(token, query=search_query, fields=field_params)
response_data: list = response['data'] # Get array of node objects
Expand Down
14 changes: 10 additions & 4 deletions app/routes/manifest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from flask import Blueprint
from flask import Blueprint, abort

from . import MAL_ID_PREFIX, IMDB_ID_PREFIX
from .utils import respond_with
from ..db.db import UID_map_collection

manifest_blueprint = Blueprint('manifest', __name__)

Expand Down Expand Up @@ -34,11 +35,16 @@
}


@manifest_blueprint.route('/<token>/manifest.json')
def addon_manifest(token: str):
@manifest_blueprint.route('/<user_id>/manifest.json')
def addon_manifest(user_id: str):
"""
Provides the manifest for the addon
:param token: The user's API token for MyAnimeList
:param user_id: The user's MyAnimeList ID
:return: JSON response
"""

user = UID_map_collection.find_one({'uid': user_id})
if not user:
return abort(404, f'User ID: {user_id} not found')

return respond_with(MANIFEST)
13 changes: 7 additions & 6 deletions app/routes/meta.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import requests
from flask import Blueprint, abort
from flask import Blueprint, abort, session

from . import mal_client, MAL_ID_PREFIX
from .manifest import MANIFEST
from .utils import mal_to_meta, respond_with
from .utils import mal_to_meta, respond_with, get_token
from ..db.db import anime_map_collection

meta = Blueprint('meta', __name__)
Expand All @@ -12,11 +11,11 @@
kitsu_API = "https://anime-kitsu.strem.fun/meta"


@meta.route('/<token>/meta/<meta_type>/<meta_id>.json')
def addon_meta(token: str, meta_type: str, meta_id: str):
@meta.route('/<user_id>/meta/<meta_type>/<meta_id>.json')
def addon_meta(user_id: str, meta_type: str, meta_id: str):
"""
Provides metadata for a specific content
:param token: The user's API token for MyAnimeList
:param user_id: The user's API token for MyAnimeList
:param meta_type: The type of metadata to return
:param meta_id: The ID of the content
:return: JSON response
Expand All @@ -25,6 +24,8 @@ def addon_meta(token: str, meta_type: str, meta_id: str):
if meta_type not in MANIFEST['types']:
abort(404)

token = get_token(user_id)

# Fetch anime details from MAL
anime_id = meta_id.replace(MAL_ID_PREFIX, '') # Extract anime id from addon meta id
field_params = 'media_type genres mean start_date end_date synopsis pictures' # Additional fields to return
Expand Down
6 changes: 3 additions & 3 deletions app/routes/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
torrentio_lite_API = "https://torrentio.strem.fun/lite/stream/"


@stream_bp.route('/<token>/stream/<stream_type>/<content_id>.json')
def addon_stream(token: str, stream_type: str, content_id: str):
@stream_bp.route('/<user_id>/stream/<stream_type>/<content_id>.json')
def addon_stream(user_id: str, stream_type: str, content_id: str):
"""
Provides a stream for a specific content
:param token: The user's API token for MyAnimeList
:param user_id: The user's MyAnimeList ID
:param stream_type: The type of stream of the content
:param content_id: The ID of the content
:return: JSON response
Expand Down
36 changes: 17 additions & 19 deletions app/routes/utils.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import random

from flask import jsonify
from flask import jsonify, abort

from app.db.db import UID_map_collection
from app.routes import MAL_ID_PREFIX


# Enable CORS
def respond_with(data):
"""
Respond with CORS headers
:param data:
:return:
"""
resp = jsonify(data)
resp.headers['Access-Control-Allow-Origin'] = "*"
resp.headers['Access-Control-Allow-Headers'] = '*'
Expand All @@ -30,39 +26,33 @@ def mal_to_meta(anime_item: dict):
# Format id to mal addon format
formatted_content_id = f"{MAL_ID_PREFIX}{content_id}"

# Get values of title, mean score, synopsis
title = anime_item.get('title', None)
mean_score = anime_item.get('mean', None)
synopsis = anime_item.get('synopsis', None)

# Check for poster key in anime_item
poster = None
if poster_objects := anime_item.get('main_picture', None):
if poster_objects := anime_item.get('main_picture', {}):
if poster := poster_objects.get('large', None):
poster = poster_objects.get('medium')

# Check for genres and format them if they exist
if genres := anime_item.get('genres', None):
if genres := anime_item.get('genres', {}):
genres = [genre['name'] for genre in genres]

# Check for release info and format it if it exists
if start_date := anime_item.get('start_date', None):
start_date = start_date[:4] # Get the year only
start_date += '-'

# Format start date if end_date of airing was not returned
if end_date := anime_item.get('end_date', None):
start_date += end_date

# Check for background key in anime_item
background = None
if picture_objects := anime_item.get('pictures', None):
# Get a random a picture from the list of picture objects
index = random.randint(0, len(picture_objects) - 1)

# Get the randomly chosen picture object's largest size if it exists else use medium
if background := picture_objects[index].get('large', None) is None:
background = picture_objects[index]['medium']
picture_objects = anime_item.get('pictures', [])
if len(picture_objects) > 0:
random_background_index = random.randint(0, len(picture_objects) - 1)
if background := picture_objects[random_background_index].get('large', None) is None:
background = picture_objects[random_background_index]['medium']

# Check for media type and filter out non series and movie types
if media_type := anime_item.get('media_type', None):
Expand All @@ -82,3 +72,11 @@ def mal_to_meta(anime_item: dict):
'releaseInfo': start_date,
'description': synopsis
}


def get_token(user_id: str):
user = UID_map_collection.find_one({'uid': user_id})
if not user:
return abort(404, 'User not found')

return user['access_token']
1 change: 1 addition & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Config:
MONGO_URI = os.getenv('MONGO_URI', "")
MONGO_DB = os.getenv('MONGO_DB', "")
MONGO_ANIME_MAP = os.getenv('MONGO_ANIME_MAP_COLLECTION', "")
MONGO_UID_MAP = os.getenv('MONGO_UID_MAP_COLLECTION', "")

# Addon API
# redirect URI depending on environment
Expand Down

0 comments on commit e0b4de0

Please sign in to comment.