lap00zza / 9anime-Companion

:rocket: A simple companion extension for 9anime
https://chrome.google.com/webstore/detail/9anime-companion/fopcehkidabibdmachbcpbgllhehknah
MIT License
88 stars 26 forks source link

Download always failing? #106

Closed awang-karisma closed 6 years ago

awang-karisma commented 6 years ago

Select your issue type: (check at least one)

Describe your issue: Is there any change in 9anime side? because when i tried to download any anime from any server, it always gives me Failed message image i dont know how to get the error message, tried installing in developer mode and checked console log, nothing appears there

lap00zza commented 6 years ago

Hi this is most likely happening because 9anime added a server query param for the url used to get videos image

I will try to find a fix for this asap.

grabberboy commented 6 years ago

@lap00zza the problem is more deeper on that

they have encode the token for the grabber url encoded using client side javascript from /all.js

$.ajax(e.grabber, { data: o, success: function(t) { i[R](uU, e.subtitle), t[Xy] ? t[Xy] === di ? r.Ei(xM + sp + di + sp + kE + sp + vP + sp + kH + sp + ST + sp + Qa + sp + jM + sp + fF + sp + n) : (r.$i(i, t[Xy], t.token), r.Fi(i, t[Xy])) : r.Xi(i, t[R]) }, error: function() { iR < 1 ? (i[R](dt, iR + 1), r.Oi(i, e)) : r.Ei(xM + sp + iz + sp + GU + sp + vP + sp + fF + sp + n + Ey + gr) } }) },

lap00zza commented 6 years ago

@grabberboy yup. I was just looking at it via the chrome debugger sometime back. Thing is this decryption happens sometime between onsuccess/oncomplete and before generating the _. But the thing is if I manually step in and trace line by line the decryption function never seems to get called (weird), My best guess is that they put a time duration check during which it can be decrypted. (manually tracing line by line gets countered by this check 😢 )

grabberboy commented 6 years ago

@lap00zza the time duration is the worst scenario lets look more on it

lap00zza commented 6 years ago

My findings till now. I got really tired after being at this for 4 hours. Might take a crack at it again tomorrow. Some of the variables are renamed. If anybody wants to continue cracking this, please feel free.

// Tracehelper
window.src = 12
window.fakeSrc = "";
Object.defineProperty(window, "src", {
    configurable: false,
    get() {
        return fakeSrc;
    },
    set(x) {
        console.trace();
        console.log(JSON.parse(JSON.stringify(x)));
        this.fakeSrc = x;
    }
});

/* --- */
// so
// -> ajax call to get link
// -> `success` calls `NI` with the JSON response and the callback `cb`
// -> `NI` calls callback `cb` after 200 ms
// -> `cb` calls `OI` with the epsiode element and `x` (contents of window.src)
//     `x` contains the decoded token and option parameter
// Final observation, js has ~200 ms to decrypt token/option param. IF it gets
// delayed Oi gets called with encrypted token/option.
// ---
// t is the episode element
function success(n) {
    Ni(n, function(x) /*name: cb*/ {
        if(x.error) 
            i.Ei("An error occured please refresh the page (ERR 1)") 
        else
            if (x.type === "direct") 
                Oi(t, x)
            else
                if (x.type === "iframe") 
                    i.Ri(x.target)
    })
}

function Ni(i, n) {
    var e = this;
    window.src = i,
    window.srcsrc = !0, /* !0 = true */
    window.setTimeout(function() {
        i = window.src,
        delete window.src,
        delete window.srcsrc,
        n.call(e, i)
    }, 200)
}

// i is the element
// e is the json object
function Oi(i, e) {
    var r = this
      , o = e.params;
    return i.data("apiErrorCount", i.data("apiErrorCount") || 0),
    $[oI](o, {
        _i: t.XTOKEN,
        mobile: n.mobile() ? 1 : 0
    }),
    $.ajax(e.grabber, {
        data: o,
        success: function(t) {
            i.data("subtitle", e.subtitle),
            if(t.error)
                if (t.error === "token") 
                    r.Ei("API: token invalid please refresh this page and try again") 
                else
                    (r.$i(i, t[Xy], t.token), r.Fi(i, t[Xy])) 
            else
                r.Xi(i, t[R])
        },
        error: function() {/*...*/}
    });
}

// second function responsible for decryption
window.setTimeout(function() {
    if(window.src)
        if(window.srcsrc === true)
            window.srcsrc = false; 
            window.src = decrypt(window.src)
}, 50);

function decrypt(t) {
    var n;
    if (Object.prototype.toString.call(t) === "[object Array]")
        for (n = 0; n < t.length; n++)
            t[n] = decrypt(t[n]);

    else if (Object.prototype.toString.call(t) === "[object Object]")
        for (n in t)
            if(Object.prototype.hasOwnProperty.call(t, n))
                t[n] = decrypt(t[n]);

    else
        typeof t === "string" && (t = t[0] === "." ? function(t, i) {
            var n, e = [];
            if (i || (i = 13),
            !(i %= 26))
                return t;
            for (n = 0,
            l = t.length; n < l; n++)
                c = t.charCodeAt(n),
                c >= 97 && c <= 122 ? e[n] = (c - 71 + i) % 26 + 97 : c >= 65 && c <= 90 ? e[n] = (c - 39 + i) % 26 + 65 : e[n] = c;
            return String.fromCharCode.apply(vA, e)
        }(t.substr(1), -18) : t);
    return t
}
/* --- */

// notes
// -> _ is calculated from the encrypted token and options
ghost commented 6 years ago

@lap00zza the encryption is rot-8. you have to decrypt all variable values starting with a dot.

  1. strip the beginning dot
  2. do rot-8
  3. done

for example: in params.token the value starts with a . and options starts with a . (dot) too. if its not a direct link but an iframe, youll see the target starts with a . and has to be decrypted too. thats it, nothing more

here in python, this method strips the dot by itself in "range(1, len(str))"

def rot8(str):
    i = -18 # line: 6850
    e = []
    for q in range(1, len(str)):
        intChar = ord(str[q])
        newChar = 0
        if 97 <= intChar <= 122:
            newChar = (intChar - 71 + i) % 26 + 97
        elif 65 <= intChar <= 90:
            newChar = (intChar - 39 + i) % 26 + 65
        else:
            newChar = intChar
        e.append(newChar)

    charArray = [chr(c) for c in e]
    return ''.join(charArray) #str(bytearray(e))
lap00zza commented 6 years ago

@hoppler WOAH thanks so much. I was having trouble identifying. Just tested, it is indeed rot-8.

Vysair commented 6 years ago

Not just failing, it doesn't even showed up like "Downloading 001" (select all or just one episode) then all "Done".

grabberboy commented 6 years ago

good findings but still the ts value is encrypted in unknown method

hope @hoppler can see this too

lap00zza commented 6 years ago

@Vysair the fix will take some time. There were 2 main hindrances:

  1. Finding the encryption scheme for token/options (which is solved)
  2. Finding the encryption scheme for ts

Once the 2nd one is done I will release a fix for the downloads. (Best guess: within max 2 days)

ghost commented 6 years ago

@lap00zza Here you go (happens all in line 12320 all.js)

def main():
    expected = "1511690400"
    ts = "YNPxYNX5YGHwYA=="

    firstCharMap = []
    secondCharMap = []
    for n in range(65, 91):
        firstCharMap.append(chr(n))
        if n % 2 != 0:
            secondCharMap.append(chr(n))
    for n in range(65, 91):
        if n % 2 == 0:
            secondCharMap.append(chr(n))

    result = ""
    for i in range(len(ts)):
        charReplaced = False
        for y in range(len(secondCharMap)):
            if ts[i] == secondCharMap[y]:
                result += firstCharMap[y]
                charReplaced = True
                break
        if not charReplaced:
            result += ts[i]
    import base64
    real_ts = base64.b64decode(result)
    print real_ts
    print "Success" if real_ts == expected else "failure"

if __name__ == '__main__':
    main()
lap00zza commented 6 years ago

Thanks so much @hoppler . For anyone reading this thread, I will release a fix for 9anime Companion tomorrow.

lap00zza commented 6 years ago

Can someone please test these artifacts: https://ci.appveyor.com/project/lap00zza/9anime-companion/build/1.4.0.24/artifacts and tell me if downloads are working for F2, G2 and G3. Install instructions can be found here: https://github.com/lap00zza/9anime-Companion/wiki/Running-in-Developement-Mode

TheMiziko commented 6 years ago

Can't wait for the fix ! unfortunately can't help with testing because I'm not at home, but hopefully the issue will be resolved till tomorrow !

debakarr commented 6 years ago

Getting this after installing. And download from F2, G2 and G3 still not working.

screenshot from 2017-11-26 20-31-15

screenshot from 2017-11-26 20-30-59

screenshot from 2017-11-26 20-31-49

grabberboy commented 6 years ago

they update again

token is not in rot8 anymore

SolvingTheWorld commented 6 years ago

Maybe they are spying on us? I wish i could help but i have been spending too much time watching anime and too little working on my JS skills (i fell bad for it).

Anyways, thanks to everyone working on solving this issue and i hope that i can help in future ones.

PradipH31 commented 6 years ago

https://twitter.com/9animeto/status/933685120539561984

They have said it

debakarr commented 6 years ago

Looking at their discord it doesn't seem like they are going to war anyone.

screenshot from 2017-11-26 22-08-54

We can only hope that this is only one-time change, and things get stabilize soon.

SolvingTheWorld commented 6 years ago

Well, that discord message dates september and the tweet is from november so it is a little outdated.

lap00zza commented 6 years ago

Probably because of a misunderstanding. One of my friend who knows the owner told me the owner thinks:

when they enabled the adblock detection I made 9anime Companion not work on purpose

But that is not at all true. It was still working if you disabled Remove Ads and Remove Social Share. But I have no clue why they added detection for social share. Ads I can understand.

SolvingTheWorld commented 6 years ago

Well, i only started to use 9anime a few days ago, it said "we stopped the pop up's please turn off the adblocker" and so did but now they are using pop ups again, there is no option to donate or pay to turn off this ads. I believe that a better service will make more donators and more people willing to help. A good way to make the service better is to coexist with this extension that makes the website much better.

awang-karisma commented 6 years ago

Well i dont know if this will help us, but i think you should remove the ability to hide the ads, maybe by doing that, we are showing to 9anime guys that we dont have intention to hurt their income

leave adblocking to the users, they can use adblock extensions

debakarr commented 6 years ago

I think @awangn6600 is right. You should probably remove the adblock feature. Also, it would be better if you could talk to the admin of 9anime and figure something out. I assure you people would love to have 9anime companion as an official extension for 9anime.

lap00zza commented 6 years ago

A fix is on the way but if possible can you guys please share your thought about remove adblocker here: https://www.reddit.com/r/9anime/comments/7ftuvp/poll_9anime_companion_remove_ads/

lap00zza commented 6 years ago

tentative fix for downloads: https://ci.appveyor.com/project/lap00zza/9anime-companion/build/1.4.0.25/artifacts

debakarr commented 6 years ago

I choose this

Remove Remove Ads from the official build (Chrome Web Store, Addons.Mozilla.Org), but still be available in a separate branch in the GitHub repository so that people can compile themselves.

The tentative fix is working.

SolvingTheWorld commented 6 years ago

Great work, thank you.

awang-karisma commented 6 years ago

@lap00zza rapidvideo removed? i always use rapid video because the download link doesnt expire quickly like google server one

is there any problem with rapidvideo?

lap00zza commented 6 years ago

@awangn6600 its target link is encrypted as well. I will patch it as soon as I find out how to decrypt.

awang-karisma commented 6 years ago

could you take give me a lead which part of the code to take a look? i will try to figure out, although it might not give any result lol

lap00zza commented 6 years ago

@awangn6600 sure. Basically you need to look in a file called all.js. But its encrypted (packed js). So you will need to wait till its decrypted and then check the called lines from the f12 Network tab. I am really not good at explaining this part 😭 . If you are trying remember to un-comment this line:

https://github.com/lap00zza/9anime-Companion/blob/dc49e4a4cfded2929d770f3e1ac1d49fe316087a/src/lib/download_all/widgets.ts#L68-L80

lap00zza commented 6 years ago

@awangn6600 lets continue the rapidvideo discussion here: #109