anime-downloader/anime_downloader/sites/animixplay.py

176 lines
7.3 KiB
Python

from anime_downloader.sites.anime import Anime, AnimeEpisode, SearchResult
from anime_downloader.sites import helpers
import base64
import json
import logging
from anime_downloader.const import HEADERS
logger = logging.getLogger(__name__)
class AniMixPlay(Anime, sitename='animixplay'):
sitename = 'animixplay'
@classmethod
def search(cls, query):
# V3 not supported
v1 = helpers.soupify(helpers.post("https://v1.nmtvjxdtx42qdwktdxjfoikjq.workers.dev/",
data={"q2": query}, verify=False).json()['result']).select('p.name > a')
v5 = helpers.soupify(helpers.post("https://cdn.animixplay.to/api/search/",
data={"qfast": query}, verify=False).json()['result']).select('p.name > a')
# v3 = helpers.soupify(helpers.post("https://v3.w0ltfgqz8y3ygjozgs4v.workers.dev/",
# data={"q3": query}, verify=False).json()['result'])
# Gives 400 error on v3 and v4 if there's no results.
# HTTPError doesn't seem to play along helpers hence why it's not expected.
try:
v4 = helpers.soupify(helpers.post("https://v4.w0ltfgqz8y3ygjozgs4v.workers.dev/",
data={"q": query}, verify=False).json()['result']).select('p.name > a')
except:
v4 = []
# Meta will be name of the key in versions_dict
versions_dict = {'v1': v1, 'v4': v4, 'v5': v5}
logger.debug('Versions: {}'.format(versions_dict))
data = []
for i in versions_dict:
for j in versions_dict[i]:
data.append(SearchResult(
title=j.text,
url='https://animixplay.com' + j.get('href'),
meta={'version': i},
meta_info={
'version_key_dubbed': '(Dub)',
}
))
return data
def _scrape_episodes(self):
url = self.url
soup = helpers.soupify(helpers.get(url))
# v1 and v3 is embedded video player
# v2 and v4 is json post request
# ALL shit below really needs refactoring!
if '/v2/' in self.url or '/v4/' in self.url:
# Uses the id in the url and encodes it twice
# NaN and N4CP9Eb6laO9N are permanent encoded variables found in
# https://animixplay.com/assets/v4.min.js
url_id = str.encode(self.url.split("/")[4])
post_id = f'NaN{base64.b64encode(url_id).decode()}N4CP9Eb6laO9N'.encode()
post_id = base64.b64encode(post_id).decode()
data_id = 'id2' if '/v4/' in self.url else 'id'
# In extremely rare cases the anime isn't loaded and must be generated by the server first
try:
data = (helpers.post('https://animixplay.com/raw/2ENCwGVubdvzrQ2eu4hBH',
data={data_id: post_id}).json())
# 400 HTTPError here
except:
if '/v4/' in self.url:
data = (helpers.post('https://animixplay.com/e4/5SkyXQULLrn9OhR',
data={'id': url.split('/')[-1]}).json())['epstream']
if '/v2' in self.url:
data = (helpers.post('https://animixplay.com/e2/T23nBBj3NfRzTQx',
data={'id': url.split('/')[-1]}).json())['epstream']
logger.debug(data)
if '/v4/' in self.url:
# Has a list of mp4 links.
return data
elif '/v2/' in self.url:
# Has elaborate list for all metadata on episodes.
episodes = []
for i in data:
info_dict = i.get('src', None)
# Looks like mp4 is always first in the list
# Sometimes it returns None
if info_dict:
episodes.append(info_dict[0].get('file', ''))
else:
episodes.append('')
return episodes
else:
# V5 and V1 are somewhat similar.
servers = self.config['v5-servers']
try:
ep_list = soup.find('div', {'id': 'epslistplace'}).get_text()
logger.debug(ep_list)
jdata = json.loads(ep_list)
if '/v1/' in self.url:
keyList = list(jdata.keys())
del keyList[0]
logger.debug(keyList)
return [jdata[x] for x in keyList if '.' in jdata[x]]
else:
for i in servers:
if jdata.get(i):
return jdata.get(i)
return
except json.decoder.JSONDecodeError:
# Link generation
url_dict = {'v5': '/e5/dZ40LAuJHZjuiWX', 'v1': '/e1/9DYiGVLD7ASqZ5p'}
if '/v5/' in self.url:
version = 'v5'
else:
version = 'v1'
# Not sure if v5 id for data works.
data = (helpers.post('https://animixplay.to' + url_dict[version],
data={'id': url.split('/')[-1]}).json())['epstream']
logger.debug('Data: {}'.format(data))
if '/v1/' in self.url:
return [data[i] for i in data if i != 'eptotal']
else:
for i in servers:
if jdata.get(i):
return jdata.get(i)
return
def _scrape_metadata(self):
self.title = helpers.soupify(helpers.get(self.url).text).select_one("span.animetitle").get_text()
class AniMixPlayEpisode(AnimeEpisode, sitename='animixplay'):
def _get_sources(self):
logger.debug(self.url)
# These could be done with dict.
if not self.url:
return ''
elif 'googleapis.com/' in self.url:
return [('no_extractor', self.url)]
elif 'https://4anime' in self.url:
# VERY DIRTY!
# As extractors cannot save headers which is essentials for 4anime this code cannot be moved
# to extractors which means it has to be copied here from _4anime.py.
self.headers = {'user-agent': HEADERS[self.hash_url(self.url, len(HEADERS))]}
resp = helpers.get(self.url, headers=self.headers)
stream_url = helpers.soupify(resp).find('div', class_='videojs-desktop').find('source')['src']
return [('no_extractor', stream_url)]
elif 'mp4upload' in self.url:
return [('mp4upload', self.url)]
elif 'streamtape' in self.url:
return [('streamtape', self.url)]
else:
return [('vidstream', self.url)]
"""
Let's say the user generates link A with user agent X.
Upon retry of command it'd normally use Link A (cached), but with user agent Y
which would error because the user agent isn't consistent.
This 'hashes' the url to generate a 'random' header which is consistent throughout multiple commands.
"""
def hash_url(self, url, length):
total = 0
for i in url:
total += ord(i)
return total % length