Skip to content

Commit

Permalink
Token aliasing using user's MAL ID
Browse files Browse the repository at this point in the history
  • Loading branch information
SageTendo committed Jan 22, 2024
1 parent d133d25 commit 6b52770
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 114 deletions.
202 changes: 113 additions & 89 deletions Pipfile.lock

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions 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
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
11 changes: 10 additions & 1 deletion app/routes/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
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


Expand Down Expand Up @@ -71,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 6b52770

Please sign in to comment.