Skip to content

Commit

Permalink
Merge pull request #9 from Oshan96/dev
Browse files Browse the repository at this point in the history
Dev - changes for v1.0.1
  • Loading branch information
Oshan96 authored Mar 30, 2020
2 parents e8209e5 + fd7dada commit 5d8b6a1
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 154 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ You can now bulk download your favourite anime episodes for various websites, in
[See supported websites](#Supported-Websites)

## Donations
If this project is helpful to you and love my work and feel like showing love/appreciation, would you like to buy me a coffee?
If this project is helpful to you and love my work and feel like showing love/appreciation, would you like to buy me a coffee?<br>
<a href="https://www.buymeacoffee.com/Oshan96" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>

## Supported Websites
| Website | Need recaptcha token? | Supported resolutions | FFMPEG needed? |
|--- |--- |--- |--- |
| [9Anime](https://9anime.to/) | Yes | Default only | No |
| [4Anime](https://4anime.to/) | No | Default only | No |
| [AnimePahe](https://animepahe.com/) | No | 720p, 1080p | No |
| [AnimeUltima](https://www.animeultima.to/) | No | 240p, 360p, 480p, 720p, 1080p | Yes |
| Website | Need recaptcha token? | Supported resolutions | FFMPEG needed? | File Size | Additional Notes |
|--- |--- |--- |--- |--- |--- |
| [9Anime](https://9anime.to/) | Yes | Default only | No | 500-600MB | Will always work, provided token |
| [4Anime](https://4anime.to/) | No | Default only | No | Around 150MB | Upon failure, visit 4anime website and restart anime downloader |
| [AnimePahe](https://animepahe.com/) | No | 720p, 1080p | No | 720p: ~150MB, 1080p: ~200MB | Anime Downloader v1.0.1 upwards(v1.0.0 no longer works). Also download speed is capped by host |
| [AnimeUltima](https://www.animeultima.to/) | No | 240p, 360p, 480p, 720p, 1080p | Yes | 1080p is around 1GB | - |

## Download Anime Downloader [Windows]
> Note : Currently only windows executable is provided (Linux, Mac users go to [Build from source](#Building-from-source))
Expand Down Expand Up @@ -279,6 +279,7 @@ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md
## Acknowledgements
Anime Downloader wouldn't be possible without these awesome free and opensource projects!
- [CloudScraper](https://github.com/VeNoMouS/cloudscraper)
- [Js2Py](https://github.com/PiotrDabkowski/Js2Py)
- [PySimpleGUI](https://github.com/PySimpleGUI/PySimpleGUI)
- [FFMPEG](https://ffmpeg.org/)

Expand Down
106 changes: 106 additions & 0 deletions anime_downloader/extractors/kwik_extractor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import re
from util.Color import printer
from time import sleep
from extractors.kwik_token_extractor import kwik_token_extractor
from bs4 import BeautifulSoup


class KwikExtractor:
def __init__(self, session, gui=None):
self.session = session
self.gui = gui
self.token = None

def __get_cookie_and_response(self, episode):
printer("INFO", "Collecting request header values...", self.gui)

head = {
"referer": episode.page_url,
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36 Edg/80.0.361.69"
}
response = self.session.get(episode.page_url, headers=head)
cookie = []
try:
cookie.append(response.headers["set-cookie"])
cookie.append(response)
except Exception as ex:
printer("ERROR", ex, self.gui)
return None

return cookie

def __set_token(self, response_text):
data = re.search("[\S]+\",[\d]+,\"[\S]+\",[\d]+,[\d]+,[\d]+", response_text).group(0)
parameters = data.split(",")
para1 = parameters[0].strip("\"")
para2 = int(parameters[1])
para3 = parameters[2].strip("\"")
para4 = int(parameters[3])
para5 = int(parameters[4])
para6 = int(parameters[5])

page_data = kwik_token_extractor.extract_data(para1, para2, para3, para4, para5, para6)
page_data = BeautifulSoup(page_data, "html.parser")

input_field = page_data.find("input", attrs={"name": "_token"})

# print(input_field)

if input_field is not None:
self.token = input_field["value"]
# print(self.token)
return True

return False

def set_direct_link(self, episode):
cookie = self.__get_cookie_and_response(episode)
if cookie is None:
printer("INFO", "Retrying header retrieval...", self.gui)
sleep(2)
cookie = self.__get_cookie_and_response(episode)

if cookie is None:
printer("ERROR", "Couldn't find headers needed ...", self.gui)
return False

# token = self.__get_token(cookie[1])

if self.token is None:
self.__set_token(cookie[1].text)

if self.token is None:
printer("ERROR", "No token found... skipping", self.gui)
return False

# print(cookie[0])
head = {
"origin": "https://kwik.cx",
"referer": episode.page_url,
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36 Edg/80.0.361.69",
"cookie": cookie[0]
}

payload = {
"_token": self.token
}

post_url = "https://kwik.cx/d/" + episode.id

# print(head)
# print(payload)
# print(post_url)

resp_headers = self.session.post(post_url, data=payload, headers=head, allow_redirects=False)
# print(resp_headers)
try:
episode.download_url = resp_headers.headers["location"]
# print(resp_headers.headers["location"])
except Exception as ex:
# print(resp_headers)
# printer("ERROR", ex, self.gui)
self.token = None
printer("ERROR", "Failed to retrieve direct url for " + episode.title, self.gui)
return False

return True
75 changes: 75 additions & 0 deletions anime_downloader/extractors/kwik_token_extractor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from js2py.pyjs import *

# setting scope
var = Scope(JS_BUILTINS)
set_global_object(var)

# Code follows:
var.registers(['extract_data', '_0xe12c'])


@Js
def PyJsHoisted__0xe12c_(d, e, f, this, arguments, var=var):
var = Scope({'d': d, 'e': e, 'f': f, 'this': this, 'arguments': arguments}, var)
var.registers(['e', 'i', 'h', 'g', 'k', 'd', 'j', 'f'])
var.put('g', Js('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/').callprop('split', Js('')))
var.put('h', var.get('g').callprop('slice', Js(0.0), var.get('e')))
var.put('i', var.get('g').callprop('slice', Js(0.0), var.get('f')))

@Js
def PyJs_anonymous_0_(a, b, c, this, arguments, var=var):
var = Scope({'a': a, 'b': b, 'c': c, 'this': this, 'arguments': arguments}, var)
var.registers(['b', 'c', 'a'])
if PyJsStrictNeq(var.get('h').callprop('indexOf', var.get('b')), (-Js(1.0))):
return var.put('a', (
var.get('h').callprop('indexOf', var.get('b')) * var.get('Math').callprop('pow', var.get('e'),
var.get('c'))), '+')

PyJs_anonymous_0_._set_name('anonymous')
var.put('j',
var.get('d').callprop('split', Js('')).callprop('reverse').callprop('reduce', PyJs_anonymous_0_, Js(0.0)))
var.put('k', Js(''))
while (var.get('j') > Js(0.0)):
var.put('k', (var.get('i').get((var.get('j') % var.get('f'))) + var.get('k')))
var.put('j', ((var.get('j') - (var.get('j') % var.get('f'))) / var.get('f')))
return (var.get('k') or Js('0'))


PyJsHoisted__0xe12c_.func_name = '_0xe12c'
var.put('_0xe12c', PyJsHoisted__0xe12c_)


@Js
def PyJsHoisted_extract_data_(h, u, n, t, e, r, this, arguments, var=var):
var = Scope({'h': h, 'u': u, 'n': n, 't': t, 'e': e, 'r': r, 'this': this, 'arguments': arguments}, var)
var.registers(['e', 'len', 'i', 'h', 'u', 'n', 'r', 's', 't', 'j'])
var.put('r', Js(''))
# for JS loop
var.put('i', Js(0.0))
var.put('len', var.get('h').get('length'))
while (var.get('i') < var.get('len')):
try:
var.put('s', Js(''))
while PyJsStrictNeq(var.get('h').get(var.get('i')), var.get('n').get(var.get('e'))):
var.put('s', var.get('h').get(var.get('i')), '+')
(var.put('i', Js(var.get('i').to_number()) + Js(1)) - Js(1))
# for JS loop
var.put('j', Js(0.0))
while (var.get('j') < var.get('n').get('length')):
try:
var.put('s', var.get('s').callprop('replace',
var.get('RegExp').create(var.get('n').get(var.get('j')),
Js('g')), var.get('j')))
finally:
(var.put('j', Js(var.get('j').to_number()) + Js(1)) - Js(1))
var.put('r', var.get('String').callprop('fromCharCode', (
var.get('_0xe12c')(var.get('s'), var.get('e'), Js(10.0)) - var.get('t'))), '+')
finally:
(var.put('i', Js(var.get('i').to_number()) + Js(1)) - Js(1))
return var.get('decodeURIComponent')(var.get('escape')(var.get('r')))


PyJsHoisted_extract_data_.func_name = 'extract_data'
var.put('extract_data', PyJsHoisted_extract_data_)

kwik_token_extractor = var.to_python()
2 changes: 1 addition & 1 deletion anime_downloader/gui/GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def create_ui(self):
[sg.ProgressBar(100, key="progress", orientation="h", size=(45, 15))]
]

self.window = sg.Window("Anime Downloader v1.0.0", layout)
self.window = sg.Window("Anime Downloader v1.0.1", layout)

def check_messages(self, values):
global i, max_val
Expand Down
Loading

0 comments on commit 5d8b6a1

Please sign in to comment.