Last active
December 30, 2020 17:29
Star
You must be signed in to star a gist
Deobfuscated Papago API (Python/Javascript/PHP) : Blocked as of December 2020
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// Ported from papago.py | |
// v1: b64_enc(rot13([:16]) + [16:]) | |
/* Derived from stackoverflow */ | |
function uuidgen() { | |
return sprintf('%08x-%04x-%04x-%04x-%04x%08x', | |
mt_rand(0, 0xffffffff), | |
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), | |
mt_rand(0, 0xffff), mt_rand(0, 0xffffffff) | |
); | |
} | |
/* Derived from stackoverflow */ | |
function contains($str, array $arr){ | |
foreach($arr as $a) { | |
if (stripos($str,$a) !== false) return true; | |
} | |
return false; | |
} | |
class PapagoCrypt { | |
private static function generate_random_string($length=8){ | |
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; | |
$charactersLength = strlen($characters); | |
$randomString = ''; | |
for ($i = 0; $i < $length; $i++) { | |
$randomString .= $characters[rand(0, $charactersLength - 1)]; | |
} | |
return $randomString; | |
} | |
private static function rotate_string_left($text, $length){ | |
return substr($text, $length) . substr($text, 0, $length); | |
} | |
private static function rotate_string_right($text, $length){ | |
return substr($text, -$length) . substr($text, 0, -$length); | |
} | |
private static function change_alphabet_order($text){ | |
$table_before = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; | |
$table_after = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"; | |
return strtr($text, $table_before, $table_after); | |
} | |
private static function strlen_multibyte($text){ | |
return strlen($text); | |
} | |
public function encrypt($plaintext){ | |
$padding_length = self::strlen_multibyte($plaintext) % 6; | |
$padding_byte = substr(self::generate_random_string(6), 0, 6 - $padding_length); | |
$random_byte = self::generate_random_string(1); | |
$encode_text = "" . $plaintext . $padding_byte; | |
$b64_text = base64_encode($encode_text); | |
$b64_size = ord($random_byte) % (strlen($b64_text) - 2) + 1; | |
$rotated_b64_text = $random_byte . self::rotate_string_left($b64_text, $b64_size); | |
return self::change_alphabet_order($rotated_b64_text); | |
} | |
public function decrypt($crypttext){ | |
$rotated_b64_text = self::change_alphabet_order($crypttext); | |
$random_byte = $rotated_b64_text[0]; | |
$b64_size = ord($random_byte) % (strlen($rotated_b64_text) - 3) + 1; | |
$b64_text = self::rotate_string_right(substr($rotated_b64_text, 1), $b64_size); | |
$plaintext = base64_decode($b64_text); | |
try { | |
if(strrpos($plaintext, "}") !== false){ | |
return substr($plaintext, 0, strrpos($plaintext, "}") + 1); | |
}else{ | |
return $plaintext; | |
} | |
}catch(Exception $e){ | |
return $plaintext; | |
} | |
} | |
} | |
class Papago { | |
private static function parse_large_text($text){ | |
$result = [""]; | |
$data = explode("\n", $text); | |
$i = 0; | |
foreach($data as $line){ | |
if(strlen($result[$i] . "\n" . $line) <= 5000){ | |
$result[$i] .= "\n" . $line; | |
}else{ | |
if(strlen($line) >= 5000){ | |
$_line = $line; | |
$full_stop = [".", "。", "।", ":", "෴", "۔"]; | |
if(!contains($_line, $full_stop)){ | |
$_line = str_split($_line, 5000); | |
array_push($result, ...$_line); | |
$i += count($_line); | |
continue; | |
} | |
foreach($full_stop as $_full_stop){ | |
$_line = explode($_full_stop, $_line); | |
$_line = implode($_full_stop . "\n", $_line); | |
$_parsed_line = self::parse_large_text($_line); | |
array_push($result, ...$_parsed_line); | |
$i += count($_parsed_line); | |
} | |
}else{ | |
$result[] = $line; | |
$i += 1; | |
} | |
} | |
} | |
return $result; | |
} | |
public function translate_full($source, $target, $text){ | |
$result = []; | |
$url = "https://papago.naver.com/apis/n2mt/translate"; | |
foreach(self::parse_large_text($text) as $_text){ | |
$payload = [ | |
"source" => $source, | |
"target" => $target, | |
"text" => $_text, | |
"deviceId" => uuidgen(), | |
]; | |
$encrypted_payload = PapagoCrypt::encrypt(json_encode((object)$payload)); | |
$data = ["data" => $encrypted_payload]; | |
$options = array( | |
"http" => array( | |
"header" => [ | |
"accept: application/json", | |
"accept-language: ko", | |
"content-type: application/x-www-form-urlencoded; charset=UTF-8", | |
"device-type: pc", | |
"sec-fetch-dest: empty", | |
"sec-fetch-mode: cors", | |
"sec-fetch-site: same-origin", | |
"x-apigw-partnerid: papago", | |
"user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) Chrome/82.0.4074.0", | |
"referer: https://papago.naver.com/", | |
], | |
"method" => "POST", | |
"content" => http_build_query($data) | |
) | |
); | |
$context = stream_context_create($options); | |
$response = file_get_contents($url, false, $context); | |
if ($response === FALSE) { continue; } | |
$result[] = json_decode($response); | |
} | |
return $result; | |
} | |
public function translate_text($source, $target, $text){ | |
$result = ""; | |
$trans_result = self::translate_full($source, $target, $text); | |
foreach($trans_result as $data){ | |
$result .= $data->translatedText; | |
} | |
return $result; | |
} | |
} | |
var_dump(Papago::translate_text('en', 'ko', 'Hello world!')); | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding: utf-8 | |
""" | |
papago.py | |
Papago Module | |
""" | |
import sys | |
import json | |
import uuid | |
import base64 | |
import random | |
import string | |
import requests | |
class PapagoCrypt: | |
""" | |
Papago Encryption Module (2020.03) | |
encrypt(text): Encrypt PapagoCrypt | |
decrypt(text): Decrypt PapagoCrypt | |
""" | |
@staticmethod | |
def generate_random_string(length): | |
""" (int) -> str | |
get random string with a given length | |
""" | |
result = "" | |
i = 0 | |
while i < length: | |
result += chr(random.randint(43, 122)) | |
i += 1 | |
return result | |
@staticmethod | |
def rotate_string_left(text, length): | |
""" (str, int) -> str | |
Shift string leftwards, remaining goes to right | |
""" | |
return text[length:] + text[0:length] | |
@staticmethod | |
def rotate_string_right(text, length): | |
""" (str, int) -> str | |
Shift string rightwards, remaining goes to left | |
""" | |
return text[-length:] + text[0:-length] | |
@staticmethod | |
def change_alphabet_order(text): | |
""" (str) -> str | |
Switch alphabet order, replace text with switched order | |
""" | |
table_before = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | |
table_after = "NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm" | |
if sys.version_info >= (3, 0): | |
return text.translate(str.maketrans(table_before, table_after)) | |
return text.translate(string.maketrans(table_before, table_after)) | |
@staticmethod | |
def strlen_multibyte(text): | |
""" (str) -> int | |
len(s), but needs to be multibyte. (python2 compatible) | |
""" | |
result = 0 | |
for char in text: | |
_char = ord(char) | |
result += 1 | |
if 2047 < _char <= 65535: | |
result += 1 | |
return result | |
def encrypt(self, plaintext): | |
""" (str) -> str | |
Encrypt text for Papago API communication | |
""" | |
padding_length = self.strlen_multibyte(plaintext) % 6 | |
padding_byte = ( | |
self.generate_random_string(6)[: 6 - padding_length] | |
if padding_length > 0 | |
else "" | |
) | |
random_byte = self.generate_random_string(1) | |
# base64 string -> rotate string based on random byte -> change alphabet order | |
encode_text = "" + plaintext + padding_byte | |
if sys.version_info >= (3, 0): | |
b64_text = base64.b64encode(encode_text.encode()).decode() | |
else: | |
b64_text = base64.b64encode(encode_text) | |
rotated_b64_text = random_byte | |
rotated_b64_text += self.rotate_string_left( | |
b64_text, ord(random_byte[0]) % (len(b64_text) - 2) + 1 | |
) | |
return self.change_alphabet_order(rotated_b64_text) | |
def decrypt(self, crypttext): | |
""" (PapagoCrypt, str) -> str | |
Decrypt text for Papago API communication | |
""" | |
# change alphabet order -> rotate string based on random byte -> base64 decode | |
rotated_b64_text = self.change_alphabet_order(crypttext) | |
random_byte = rotated_b64_text[0] | |
b64_text = self.rotate_string_right( | |
rotated_b64_text[1:], | |
(ord(random_byte[0]) % (len(rotated_b64_text) - 3) + 1), | |
) | |
if sys.version_info >= (3, 0): | |
plaintext = base64.b64decode(b64_text).decode() | |
else: | |
plaintext = base64.b64decode(b64_text) | |
try: | |
return plaintext[0 : plaintext.rindex("}") + 1] | |
except IndexError: | |
return plaintext | |
class Papago: | |
""" | |
Papago API Module | |
""" | |
@staticmethod | |
def parse_large_text(text): | |
""" (str) -> list | |
Parse large text (5000 bytes+) based on paragraphs. | |
""" | |
max_size = 5000 | |
result = [""] | |
data = text.split("\n") | |
i = 0 | |
for line in data: | |
if len(result[i] + "\n" + line) <= max_size: | |
result[i] += "\n" + line | |
else: | |
# if line is larger than 5000, split by dots | |
if len(line) >= max_size: | |
_line = line | |
_line_len = len(line) | |
for full_stop in [".", "。", "।", ":", "෴", "۔"]: | |
_line = "\n".join( | |
[i + full_stop for i in line.split(full_stop)] | |
) | |
# if size is not changed | |
if _line_len == len(_line): | |
_line = [_line[i:i + max_size] for i in range(0, len(line), max_size)] | |
result.extend(_line) | |
i += len(_line) | |
continue | |
_parsed_line = Papago.parse_large_text(_line) | |
result.extend(_parsed_line) | |
i += len(_parsed_line) | |
else: | |
result.append(line) | |
i += 1 | |
return result | |
@classmethod | |
def translate_full(cls, source, target, text): | |
""" (Papago, str, str, str) -> str | |
Translate Text (5000 chars maximum) | |
""" | |
result = [] | |
headers = { | |
"accept": "application/json", | |
"accept-language": "ko", | |
"content-type": "application/x-www-form-urlencoded; charset=UTF-8", | |
"device-type": "pc", | |
"sec-fetch-dest": "empty", | |
"sec-fetch-mode": "cors", | |
"sec-fetch-site": "same-origin", | |
"x-apigw-partnerid": "papago", | |
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) Chrome/82.0.4074.0", | |
"referer": "https://papago.naver.com/", | |
} | |
url = "https://papago.naver.com/apis/n2mt/translate" | |
for _text in cls.parse_large_text(text): | |
data = { | |
"source": source, | |
"target": target, | |
"text": _text, | |
"deviceId": str(uuid.uuid4()), | |
} | |
encrypted_data = PapagoCrypt().encrypt(json.dumps(data)) | |
result.append(requests.post(url, "data=%s" % (encrypted_data), headers=headers).json()) | |
return result | |
@classmethod | |
def translate_text(cls, source, target, text): | |
""" (Papago, str) -> str | |
Translate Text (text data only.) | |
""" | |
result = "" | |
trans_result = cls.translate_full(source, target, text) | |
for data in trans_result: | |
result += data['translatedText'] | |
return result | |
if __name__ == "__main__": | |
# BIG_FILE = open("long_text.txt", "r").read() | |
# print(Papago.translate_text('en', 'ko', BIG_FILE)) | |
print(Papago.translate_text('ja', 'ko', 'みくる可愛い')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Just for decrypting the API.. | |
*/ | |
// derived from https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript | |
var uuidv4 = function() { | |
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | |
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); | |
return v.toString(16); | |
}); | |
} | |
// deobfuscated from papago.naver.com | |
var papago_encrypt = function(plaintext) { | |
"use strict"; | |
function generate_random_string(len) { | |
// get random string with size `len` | |
for (var result = "", i = 0; i < len; i++) | |
result += String.fromCharCode(Math.floor(80 * Math.random() + 43)) | |
return result; | |
} | |
function rotate_string_left(str, len) { | |
// rotate string `n` times char, removed elements go right | |
return str.substr(len) + str.substr(0, len); | |
} | |
function change_alphabet_order(str) { | |
// change alphabet order from the string | |
// > g('abcdefghijklmnopqrstuvwxyz') | |
// < "nopqrstuvwxyzabcdefghijklm" | |
return str.replace(/([a-m])|([n-z])/gi, function(e, a, t) { | |
return String.fromCharCode(a ? a.charCodeAt(0) + 13 : t ? t.charCodeAt(0) - 13 : 0) || e | |
}); | |
} | |
function strlen_multibyte(str) { | |
// multibyte strlen. Korean and Japanese are counted as 3 | |
// > strlen_multibyte('mikuru미쿠루みくる') | |
// < 24 | |
for (var result = str.length, i = str.length - 1; i >= 0; i--) { | |
var _char = str.charCodeAt(i); | |
_char > 127 && _char <= 2047 ? result++ : _char > 2047 && _char <= 65535 && (result += 2) | |
} | |
return result; | |
} | |
var a = strlen_multibyte(plaintext) % 6 | |
var t = a > 0 ? generate_random_string(6).substr(0, 6 - a) : ""; | |
var encoded_text = btoa("" + plaintext + t); | |
var random_byte = generate_random_string(1); | |
var rotated_encoded_text = random_byte + rotate_string_left(encoded_text, random_byte.charCodeAt(0) % (encoded_text.length - 2) + 1); | |
return change_alphabet_order(rotated_encoded_text); | |
} | |
// Rewritten code to decrypt encryption | |
var papago_decrypt = function(crypttext) { | |
"use strict"; | |
function generate_random_string(len) { | |
// get random string with size `len` | |
for (var result = "", i = 0; i < len; i++) | |
result += String.fromCharCode(Math.floor(80 * Math.random() + 43)) | |
return result; | |
} | |
function rotate_string_right(str, len) { | |
// rotate string `n` times char, removed elements go left | |
// return str.substr(len) + str.substr(0, len); | |
return str.slice(-len) + str.substr(0, str.length - len); | |
} | |
function change_alphabet_order(str) { | |
// change alphabet order from the string | |
// > g('abcdefghijklmnopqrstuvwxyz') | |
// < "nopqrstuvwxyzabcdefghijklm" | |
return str.replace(/([a-m])|([n-z])/gi, function(e, a, t) { | |
return String.fromCharCode(a ? a.charCodeAt(0) + 13 : t ? t.charCodeAt(0) - 13 : 0) || e | |
}); | |
} | |
function strlen_multibyte(str) { | |
// multibyte strlen. Korean and Japanese are counted as 3 | |
// > strlen_multibyte('mikuru미쿠루みくる') | |
// < 24 | |
for (var result = str.length, i = str.length - 1; i >= 0; i--) { | |
var _char = str.charCodeAt(i); | |
_char > 127 && _char <= 2047 ? result++ : _char > 2047 && _char <= 65535 && (result += 2) | |
} | |
return result; | |
} | |
var rotated_text = change_alphabet_order(crypttext); | |
var random_byte = rotated_text[0]; | |
var reordered_text = rotate_string_right(rotated_text.substring(1), random_byte.charCodeAt(0) % (rotated_text.substring(1).length - 2) + 1); | |
var plaintext = atob(reordered_text); | |
if(plaintext.startsWith("{")){ | |
try{ | |
return plaintext.substring(0, plaintext.lastIndexOf("}")).concat("}"); | |
}catch(e){ } | |
} | |
return plaintext; | |
} | |
payload = { | |
"source": "en", | |
"target": "ko", | |
"text": "hi", | |
"deviceId": uuidv4() | |
} | |
fetch("https://papago.naver.com/apis/n2mt/translate", | |
{ | |
"headers": { | |
"accept": "application/json", | |
"accept-language": "ko", | |
"content-type": "application/x-www-form-urlencoded; charset=UTF-8", | |
"device-type": "pc", | |
"sec-fetch-dest": "empty", | |
"sec-fetch-mode": "cors", | |
"sec-fetch-site": "same-origin", | |
"x-apigw-partnerid": "papago" | |
}, | |
"referrer": "https://papago.naver.com/", | |
"referrerPolicy": "origin", | |
"body": "data=" + papago_encrypt(JSON.stringify(payload)), | |
"method": "POST", | |
"mode": "cors", | |
"credentials": "include" | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment