Skip to content

Instantly share code, notes, and snippets.

@Nominom
Last active July 17, 2025 11:49
Show Gist options
  • Save Nominom/634f27db499c04ae08f3962ce6cd9a4d to your computer and use it in GitHub Desktop.
Save Nominom/634f27db499c04ae08f3962ce6cd9a4d to your computer and use it in GitHub Desktop.
New version of pocket.shonenmagazine.com ripper
# Original script by drdaxxy
# https://gist.github.com/drdaxxy/1e43b3aee3e08a5898f61a45b96e4cb4
# Thanks to ToshiroScan for fixing after a shonen update broke this.
# And QPDEH for helping fix the black bars issue with some manga.
# This script has been tested to work with python 3.10
# To install required libraries:
#
# pip install requests
# pip install Pillow
# pip install beautifulsoup4
import sys
import os
import requests
import errno
import json
from PIL import Image
from bs4 import BeautifulSoup
login = False # Set this to True if you want to login
username = "your email here"
password = "your password"
loginheaders = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0',
'origin': 'https://pocket.shonenmagazine.com',
'x-requested-with': 'XMLHttpRequest'
}
loginurl = "https://pocket.shonenmagazine.com/user_account/login"
sess = requests.Session()
if login:
logindata = {"email_address": username, "password": password, "return_location_path" : "/"}
r = sess.post(loginurl, headers=loginheaders, data = logindata)
if r.ok:
print('LOGIN SUCCESS')
print(sess.cookies)
else:
print('LOGIN FAILED')
print(r.headers)
print(r.status_code)
print(r.reason)
print(r.text)
if len(sys.argv) != 3:
print("usage: shonenripperjson.py <url> <destination folder>")
sys.exit(1)
destination = sys.argv[2]
if not os.path.exists(destination):
try:
os.makedirs(destination)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
url = sys.argv[1]
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0'}
# If soup method below doesn't work, try uncommenting these 2 lines
# if not url.endswith('.json'):
# url = url + ".json"
print("Getting from url: "+url)
r = sess.get(url=url, headers=headers)
# And this
# data = r.json()
# and comment from here ========
soup = BeautifulSoup(r.content, 'html.parser')
script_tag = soup.find('script', id='episode-json')
if script_tag:
json_data = script_tag['data-value']
data = json.loads(json_data)
else:
print("No <script> with ID 'episode-json' found.")
sys.exit(1)
# to here ======================
def dlImage(url, outFilename, drm):
r = sess.get(url, stream=True, headers=headers)
if not r.ok:
print(r)
return
content_type = r.headers.get('content-type')
if content_type == "image/jpeg":
outFilename = outFilename + ".jpg"
elif content_type == "image/png":
outFilename = outFilename + ".png"
else:
print("content type not recognized!")
print(r)
return
with open(outFilename, 'wb') as file:
for block in r.iter_content(1024):
if not block:
break
file.write(block)
if drm == True:
source = Image.open(outFilename)
dest = source.copy()
def draw_subimage(sx, sy, sWidth, sHeight, dx, dy):
rect = source.crop((sx, sy, sx+sWidth, sy+sHeight))
dest.paste(rect, (dx, dy, dx+sWidth, dy+sHeight))
DIVIDE_NUM = 4
MULTIPLE = 8
cell_width = (source.width // (DIVIDE_NUM * MULTIPLE)) * MULTIPLE
cell_height = (source.height // (DIVIDE_NUM * MULTIPLE)) * MULTIPLE
for e in range(0, DIVIDE_NUM * DIVIDE_NUM):
t = e // DIVIDE_NUM * cell_height
n = e % DIVIDE_NUM * cell_width
r = e // DIVIDE_NUM
i_ = e % DIVIDE_NUM
u = i_ * DIVIDE_NUM + r
s = u % DIVIDE_NUM * cell_width
c = (u // DIVIDE_NUM) * cell_height
draw_subimage(n, t, cell_width, cell_height, s, c)
dest.save(outFilename)
if 'readableProduct' in data:
readableProduct = data['readableProduct']
nextReadableProductUri = None
if 'nextReadableProductUri' in readableProduct:
nextReadableProductUri = readableProduct['nextReadableProductUri']
if 'pageStructure' in readableProduct:
pageStructure = readableProduct['pageStructure']
if pageStructure == None:
print('Could not download pages. Most likely this volume is not public.')
sys.exit(1)
choJuGiga = pageStructure['choJuGiga'] if 'choJuGiga' in pageStructure else ''
print('choJuGiga: ', choJuGiga)
drm = choJuGiga != "usagi"
pages = pageStructure['pages'] if 'pages' in pageStructure else []
if len(pages) == 0:
print("No pages found")
sys.exit(1)
pageIndex = 0
for page in pages:
if 'src' in page:
src = page['src']
print(src)
pageIndex += 1
outFile = os.path.join(destination, f"{pageIndex:04d}")
dlImage(src, outFile, drm)
else:
print('could not find pageStructure from json response')
sys.exit(1)
if nextReadableProductUri != None:
print("Next URI: ", nextReadableProductUri)
else:
print('could not find readableProduct from json response')
@MandyHiman
Copy link

image
I went to download a paid one
It said

@Nominom
Copy link
Author

Nominom commented Jan 1, 2025

I went to download a paid one It said

Did you login in the script?

@LA20000
Copy link

LA20000 commented Mar 13, 2025

hi
Screenshot 2025-03-13 092525

I went to download a paid one
It didn't work.
i have login in the script.
Where could the problem be?

@ChrisB9
Copy link

ChrisB9 commented Mar 13, 2025

@Nominom I wanted to thank you a lot, for this script!
I adapted it, rewrote it in rust and added an automated epub conversion on top - its still work in progress but in principle it works
https://github.com/ChrisB9/manga_epub_creator
I will totally credit you on the readme once I wrote one and also mention you about the drm-part that I 1:1 copied

@MandyHiman
Copy link

I went to download a paid one It said

Did you login in the script?

Yes, i did. It also worked on already released free chapter. But didnt work on the teased next chapter that was releasing for free next week

@Nominom
Copy link
Author

Nominom commented Mar 13, 2025

@LA20000, @MandyHiman, I'll see if I can figure out what's wrong. I haven't bought any chapters myself, so it's a bit hard to test 😄

@ChrisB9 Thanks for the thanks! Be sure to credit the original creator of the script as well; this version is just an updated version from here: https://gist.github.com/drdaxxy/1e43b3aee3e08a5898f61a45b96e4cb4

The DRM code is basically straight from there.

@Nominom
Copy link
Author

Nominom commented Mar 13, 2025

@LA20000 @MandyHiman I bought a few chapters to test, and the script seems to work just fine (even for the more expensive, latest episodes). Please double-check that your script is logging in correctly. It should say something like:

...
LOGIN SUCCESS
<RequestsCookieJar[<Cookie glsc=<SECRETCOOKIEHERE> for pocket.shonenmagazine.com/>]>
Getting from URL: <urlhere>
...

If you've bought the chapter, the script logs in correctly, and it still fails to download, you can see what it says if you add print('data:' + json.dumps(data)) to line 163. Here:

        if pageStructure == None:
            print('Could not download pages. Most likely this volume is not public.')
+           print('data:' + json.dumps(data))
            sys.exit(1)

For me, it has "hasPurchased": false and "isPublic": false for chapters that I have not bought.

@QPDEH
Copy link

QPDEH commented Jun 1, 2025

They quite changed the site (calculating hash for request, new drm method, new json scheme, etc.). I wrote and tested working code on some free titles but it will fail on a paid one for sure due to api changes.

code
#Original script by drdaxxy
# https://gist.github.com/drdaxxy/1e43b3aee3e08a5898f61a45b96e4cb4

# Thanks to ToshiroScan for fixing after a shonen update broke this.
# And QPDEH for helping fix the black bars issue with some manga.
# This script has been tested to work with python 3.10

# To install required libraries: 
#
# pip install requests
# pip install Pillow

import sys
import os
import requests
import errno
import json
import hashlib
from PIL import Image

login = False # Set this to True if you want to login
username = "your email here"
password = "your password"


loginheaders = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0',
    'origin': 'https://pocket.shonenmagazine.com',
    'x-requested-with': 'XMLHttpRequest'
}

loginurl = "https://pocket.shonenmagazine.com/user_account/login"

sess = requests.Session()

if login:

    logindata = {"email_address": username, "password": password, "return_location_path" : "/"}

    r = sess.post(loginurl, headers=loginheaders, data = logindata)

    if r.ok:

        print('LOGIN SUCCESS')
        print(sess.cookies)
    else:
        print('LOGIN FAILED')
        print(r.headers)
        print(r.status_code)
        print(r.reason)
        print(r.text)



if len(sys.argv) != 3:
    print("usage: shonenripperjson.py <episode id> <destination folder>")
    sys.exit(1)


destination = sys.argv[2]

if not os.path.exists(destination):
    try:
        os.makedirs(destination)
    except OSError as exc:
        if exc.errno != errno.EEXIST:
            raise

episode_id = sys.argv[1]

def mangahash(episode_id):
    e = {"platform": "3", "episode_id": episode_id}
    t = sorted(list(e.keys()))
    def Ed(e, t): return dc(e) + "_" + _h(t)
    def dc(a): return hashlib.sha256(a.encode()).hexdigest()
    def _h(a): return hashlib.sha512(a.encode()).hexdigest()
    r = []
    for u in t: r.append(Ed(u, e[u]))
    n = dc(",".join(r))
    return _h(n + Ed("", ""))

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0'}
apiheaders = {
    'x-manga-is-crawler': "false",
    'x-manga-hash': mangahash(episode_id)  # w/o this header site won't return links to images
}

print("Getting from episode: " + episode_id)
r = sess.get(url="https://api.pocket.shonenmagazine.com/web/episode/viewer?platform=3&episode_id=" + episode_id, headers={**headers, **apiheaders})
data = r.json()

#with open("resp.json", "w+", encoding="utf-8") as file_:file_.write(r.text)

if not r.ok:
    print("Invalid request")
    print(r)
    sys.exit(1)


def dlImage(url, outFilename, drm, drmkey):
    r = sess.get(url, stream=True, headers=headers)

    if not r.ok:
        print(r)
        return

    content_type = r.headers.get('content-type')
    if content_type == "image/jpeg":
        outFilename = outFilename + ".jpg"
    elif content_type == "image/png":
        outFilename = outFilename + ".png"
    else:
        print("content type not recognized!")
        print(r)
        return

    with open(outFilename, 'wb') as file:
        for block in r.iter_content(1024):
            if not block:
                break
            file.write(block)

    if drm == True:
        source = Image.open(outFilename)
        dest = source.copy()
        
        def draw_subimage(sx, sy, sWidth, sHeight, dx, dy):
            rect = source.crop((sx, sy, sx+sWidth, sy+sHeight))
            dest.paste(rect, (dx, dy, dx+sWidth, dy+sHeight))

        def ve(num):
            def rshift(val, n): return (val % 0x100000000) >> n
            e = num
            while True:
                e ^= (e << 13)
                e ^= rshift(e, 17)
                e ^= (e << 5)
                e %= 2**32
                yield e

        DIVIDE_NUM = 4
        MULTIPLE = 8
        cell_width = (source.width // (DIVIDE_NUM * MULTIPLE)) * MULTIPLE
        cell_height = (source.height // (DIVIDE_NUM * MULTIPLE)) * MULTIPLE
        s = ve(drmkey)
        for e in enumerate(map(lambda x:x[1], sorted(map(lambda x:(next(s), x), range(DIVIDE_NUM*DIVIDE_NUM))))):
            t = e[1] // DIVIDE_NUM * cell_height
            n = e[1] % DIVIDE_NUM * cell_width
            s = e[0] % DIVIDE_NUM * cell_width
            c = (e[0] // DIVIDE_NUM) * cell_height
            draw_subimage(n, t, cell_width, cell_height, s, c)

        dest.save(outFilename)

nextEpisodeId = None

if 'next_episode' in data and 'episode_id' in data['next_episode']:
    nextEpisodeId = data['next_episode']['episode_id']

pages = data['page_list'] if 'page_list' in data else []

if len(pages) == 0:
    print("No pages found")
    sys.exit(1)

if data.get('scramble_seed') == None:
    print('Could not find scramble seed.')
    sys.exit(1)

#choJuGiga = data['choJuGiga'] if 'choJuGiga' in data else ''
#print('choJuGiga: ', choJuGiga)
#drm = choJuGiga != "usagi"
drm = True
drmkey = data['scramble_seed']

pageIndex = 0

for page in pages:
    src = page
    print(src)
    pageIndex += 1
    outFile = os.path.join(destination, f"{pageIndex:04d}")
    dlImage(src, outFile, drm, drmkey)

if nextEpisodeId != None:
    print("Next episode:", nextEpisodeId)

Now script requires episode id (YYY in url like https://pocket.shonenmagazine.com/titles/XXX/episode/YYY) instead of url to chapter.

@zaknenou
Copy link

They quite changed the site (calculating hash for request, new drm method, new json scheme, etc.). I wrote and tested working code on some free titles but it will fail on a paid one for sure due to api changes.
code

Now script requires episode id (YYY in url like https://pocket.shonenmagazine.com/titles/XXX/episode/YYY) instead of url to chapter.

King

@RobocoCh
Copy link

There is an error when the program tries to login and the login ends up failing, I have installed the necessary libraries.
I tried to download the latest and paid chapter for kaoru hana manga but couldn't, at the end of the process it indicated invalid response.

LOGIN FAILED {'Content-Type': 'text/html;charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Date': 'Thu, 17 Jul 2025 11:46:05 GMT', 'vary': 'Accept-Encoding', 'x-powered-by': 'Nuxt', 'X-Robots-Tag': 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1', 'content-encoding': 'gzip', 'X-Cache': 'Error from cloudfront', 'Via': '1.1 367e80e95c5ef14d28af079c4ff4dfbc.cloudfront.net (CloudFront)', 'X-Amz-Cf-Pop': 'CGK51-P1', 'X-Amz-Cf-Id': 'WMH5JAPVfSrE6roDL3vHkgo1LUQfYinCIT-NXD_zBUDzIk80Qx7n1A==', 'X-XSS-Protection': '1; mode=block', 'X-Frame-Options': 'SAMEORIGIN', 'Referrer-Policy': 'strict-origin-when-cross-origin', 'X-Content-Type-Options': 'nosniff', 'Strict-Transport-Security': 'max-age=31536000'} 404 Page not found: /user_account/login

Getting from episode: https://pocket.shonenmagazine.com/title/01524/episode/420176 Invalid request <Response [400]>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment