I've debugged quite a lot, and the error isn't on OpenSubtitles side anymore, it's on the browser.
I suspect the "popup" blocker to be an issue, or some other builtin protection system, for example restrictions on DOMParser.
How ? Well, it works in Chrome, in Firefox "incognito mode", and in Edge, but not with Firefox, it throws a NS_ERROR_FAILURE that's really hard to debug (just google that and be amazed :p)
For you to test: this html contains everything, just save code to test.html and open in a browser
Code: Select all
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Test XML-RPC | OpenSubtitles.org</title>
</head>
<body>
<div>
IMDBID: <input id="imdbid" value="tt0462499">
List subtitles in <input id="langcode" value="eng">
<button onClick="search()">Search!</button>
</div>
<br>
<!-- subtitle list -->
<div id="subtitles"></div>
<br>
<!-- debug header -->
<div id="headers" style="display:none">
<strong>headers:</strong>
<br>
<code id="heads"></code>
</div>
<br>
<!-- errors -->
<div id="error"></div>
<script>
// XML-RPC function (source: http://mastermov.com/cdn/js/mimic.js)
function XmlRpc() {}
function XmlRpcRequest(e, t) {
this.serviceUrl = e, this.methodName = t, this.crossDomain = !1, this.withCredentials = !1, this.params = [], this.headers = {}
}
function XmlRpcResponse(e, method) {
var headers = e.getAllResponseHeaders();
document.getElementById('heads').innerHTML += "<span style='text-decoration:underline'>"+method+":</underline>" + "<br>";
document.getElementById('heads').innerHTML += headers.replace(/\n/g, '<br>') + "<br>";
document.getElementById('headers').style.display = 'block';
this.xmlData = e.responseXML;
}
function Builder() {}
function Base64(e) {
this.bytes = e;
}
XmlRpc.PROLOG = '<?xml version="1.0" encoding="utf-8" ?>\n', XmlRpc.REQUEST = "<methodCall>\n<methodName>${METHOD}</methodName>\n<params>\n${DATA}</params>\n</methodCall>", XmlRpc.PARAM = "<param>\n<value>\n${DATA}</value>\n</param>\n", XmlRpc.ARRAY = "<array>\n<data>\n${DATA}</data>\n</array>\n", XmlRpc.STRUCT = "<struct>\n${DATA}</struct>\n", XmlRpc.MEMBER = "<member>\n${DATA}</member>\n", XmlRpc.NAME = "<name>${DATA}</name>\n", XmlRpc.VALUE = "<value>\n${DATA}</value>\n", XmlRpc.SCALAR = "<${TYPE}>${DATA}</${TYPE}>\n", XmlRpc.getDataTag = function(e) {
try {
var t = typeof e;
switch (t.toLowerCase()) {
case "number":
t = Math.round(e) == e ? "int" : "double";
break;
case "object":
t = e.constructor == Base64 ? "base64" : e.constructor == String ? "string" : e.constructor == Boolean ? "boolean" : e.constructor == Array ? "array" : e.constructor == Date ? "dateTime.iso8601" : e.constructor == Number ? Math.round(e) == e ? "int" : "double" : "struct"
}
return t
} catch (a) {
return null
}
}, XmlRpc.getTagData = function(e) {
var t = null;
switch (e) {
case "struct":
t = new Object;
break;
case "array":
t = new Array;
break;
case "datetime.iso8601":
t = new Date;
break;
case "boolean":
t = new Boolean;
break;
case "int":
case "i4":
case "double":
t = new Number;
break;
case "string":
t = new String;
break;
case "base64":
t = new Base64
}
return t
}, XmlRpcRequest.prototype.addParam = function(e) {
var t = typeof e;
switch (t.toLowerCase()) {
case "function":
return;
case "object":
if (!e.constructor.name) return
}
this.params.push(e)
}, XmlRpcRequest.prototype.clearParams = function() {
this.params.splice(0, this.params.length)
}, XmlRpcRequest.prototype.setHeader = function(e, t) {
t ? this.headers[e] = t : delete this.headers[e]
}, XmlRpcRequest.prototype.send = function() {
var e, t, a = "", s = 0;
for (s = 0; s < this.params.length; s++) {
a += XmlRpc.PARAM.replace("${DATA}", this.marshal(this.params[s]));
}
e = XmlRpc.REQUEST.replace("${METHOD}", this.methodName);
e = XmlRpc.PROLOG + e.replace("${DATA}", a);
t = Builder.buildXHR(this.crossDomain);
t.open("POST", this.serviceUrl, !1);
for (s in this.headers) {
if (this.headers.hasOwnProperty(s)) {
t.setRequestHeader(s, this.headers[s]);
}
}
return this.withCredentials && "withCredentials" in t && (t.withCredentials = !0), t.send(Builder.buildDOM(e)), new XmlRpcResponse(t, this.methodName);
}, XmlRpcRequest.prototype.marshal = function(e) {
var t, a, s, r = XmlRpc.getDataTag(e),
n = XmlRpc.SCALAR.replace(/\$\{TYPE\}/g, r),
i = "";
switch (r) {
case "struct":
s = "";
for (a in e) t = "", t += XmlRpc.NAME.replace("${DATA}", a), t += XmlRpc.VALUE.replace("${DATA}", this.marshal(e[a])), s += XmlRpc.MEMBER.replace("${DATA}", t);
i = XmlRpc.STRUCT.replace("${DATA}", s);
break;
case "array":
for (t = "", a = 0; a < e.length; a++) t += XmlRpc.VALUE.replace("${DATA}", this.marshal(e[a]));
i = XmlRpc.ARRAY.replace("${DATA}", t);
break;
case "dateTime.iso8601":
i = n.replace("${DATA}", e.toIso8601());
break;
case "boolean":
i = n.replace("${DATA}", 1 == e ? 1 : 0);
break;
case "base64":
i = n.replace("${DATA}", e.encode());
break;
default:
i = n.replace("${DATA}", e)
}
return i
}, XmlRpcResponse.prototype.isFault = function() {
return this.faultValue
}, XmlRpcResponse.prototype.parseXML = function() {
var e, t;
for (t = this.xmlData.childNodes.length, this.faultValue = void 0, this.currentIsName = !1, this.propertyName = "", this.params = [], e = 0; t > e; e++) this.unmarshal(this.xmlData.childNodes[e], 0);
return this.params[0]
}, XmlRpcResponse.prototype.unmarshal = function(e, t) {
var a, s, r, n;
if (1 == e.nodeType) {
switch (a = null, s = e.tagName.toLowerCase()) {
case "fault":
this.faultValue = !0;
break;
case "name":
this.currentIsName = !0;
break;
default:
a = XmlRpc.getTagData(s)
}
if (null != a && (this.params.push(a), "struct" == s || "array" == s)) {
if (this.params.length > 1) switch (XmlRpc.getDataTag(this.params[t])) {
case "struct":
this.params[t][this.propertyName] = this.params[this.params.length - 1];
break;
case "array":
this.params[t].push(this.params[this.params.length - 1])
}
t = this.params.length - 1
}
for (n = e.childNodes.length, r = 0; n > r; r++) this.unmarshal(e.childNodes[r], t)
}
if (3 == e.nodeType && /[^\t\n\r ]/.test(e.nodeValue))
if (1 == this.currentIsName) this.propertyName = e.nodeValue, this.currentIsName = !1;
else {
switch (XmlRpc.getDataTag(this.params[this.params.length - 1])) {
case "dateTime.iso8601":
this.params[this.params.length - 1] = Date.fromIso8601(e.nodeValue);
break;
case "boolean":
this.params[this.params.length - 1] = "1" == e.nodeValue ? !0 : !1;
break;
case "int":
case "double":
this.params[this.params.length - 1] = new Number(e.nodeValue);
break;
case "string":
this.params[this.params.length - 1] = new String(e.nodeValue);
break;
case "base64":
this.params[this.params.length - 1] = new Base64(e.nodeValue)
}
if (this.params.length > 1) switch (XmlRpc.getDataTag(this.params[t])) {
case "struct":
this.params[t][this.propertyName] = this.params[this.params.length - 1];
break;
case "array":
this.params[t].push(this.params[this.params.length - 1])
}
}
}, Builder.buildXHR = function(e) {
return e ? "undefined" != typeof XDomainRequest ? new XDomainRequest : new XMLHttpRequest : "undefined" != typeof XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject("Microsoft.XMLHTTP")
}, Builder.buildDOM = function(e) {
var t, a, s, r;
if ("undefined" != typeof DOMParser) {
t = new DOMParser;
r = t.parseFromString(e, "text/xml");
return r;
} else {
for (a = ["Microsoft.XMLDOM", "MSXML2.DOMDocument", "MSXML.DOMDocument"], s = 0; s < a.length; s++) {
try {
return t = new ActiveXObject(a[s]), t.loadXML(e), t;
} catch (r) {
throw r;
}
}
}
}, Date.prototype.toIso8601 = function() {
var e = this.getYear(),
t = this.getMonth() + 1,
a = this.getDate(),
s = this.toTimeString().substr(0, 8);
return 1900 > e && (e += 1900), 10 > t && (t = "0" + t), 10 > a && (a = "0" + a), e + t + a + "T" + s
}, Date.fromIso8601 = function(e) {
var t = e.substr(0, 4),
a = e.substr(4, 2),
s = e.substr(6, 2),
r = e.substr(9, 2),
n = e.substr(12, 2),
i = e.substr(15, 2);
return new Date(t, a - 1, s, r, n, i, 0)
}, Base64.CHAR_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", Base64.prototype.encode = function() {
if ("function" == typeof btoa) return btoa(this.bytes);
var e = [],
t = [],
a = [],
s = 0,
r = 0;
for (r = 0; r < this.bytes.length; r += 3) e[0] = this.bytes.charCodeAt(r), e[1] = this.bytes.charCodeAt(r + 1), e[2] = this.bytes.charCodeAt(r + 2), t[0] = e[0] >> 2, t[1] = (3 & e[0]) << 4 | e[1] >> 4, t[2] = (15 & e[1]) << 2 | e[2] >> 6, t[3] = 63 & e[2], isNaN(e[1]) ? t[2] = t[3] = 64 : isNaN(e[2]) && (t[3] = 64), a[s++] = Base64.CHAR_MAP.charAt(t[0]) + Base64.CHAR_MAP.charAt(t[1]) + Base64.CHAR_MAP.charAt(t[2]) + Base64.CHAR_MAP.charAt(t[3]);
return a.join("")
}, Base64.prototype.decode = function() {
if ("function" == typeof atob) return atob(this.bytes);
for (var e = [], t = [], a = [], s = 0, r = 0; this.bytes.length % 4 != 0;) this.bytes += "=";
for (r = 0; r < this.bytes.length; r += 4) t[0] = Base64.CHAR_MAP.indexOf(this.bytes.charAt(r)), t[1] = Base64.CHAR_MAP.indexOf(this.bytes.charAt(r + 1)), t[2] = Base64.CHAR_MAP.indexOf(this.bytes.charAt(r + 2)), t[3] = Base64.CHAR_MAP.indexOf(this.bytes.charAt(r + 3)), e[0] = t[0] << 2 | t[1] >> 4, e[1] = (15 & t[1]) << 4 | t[2] >> 2, e[2] = (3 & t[2]) << 6 | t[3], a[s++] = String.fromCharCode(e[0]), 64 != t[2] && (a[s++] = String.fromCharCode(e[1])), 64 != t[3] && (a[s++] = String.fromCharCode(e[2]));
return a.join("")
};
function searchSubtitles() {
document.getElementById('subtitles').innerHTML = '';
document.getElementById('heads').innerHTML = '';
document.getElementById('error').innerHTML = '';
document.getElementById('headers').style.display = 'none';
// OS LogIn
var loginRequest = new XmlRpcRequest("http://api.opensubtitles.org/xml-rpc", "LogIn");
loginRequest.params = (['', '', 'eng', 'FileBot']);
var response1 = loginRequest.send();
var token = String(response1.parseXML().token);
// OS SearchSubtitles
var searchRequest = new XmlRpcRequest("http://api.opensubtitles.org/xml-rpc",
"SearchSubtitles");
searchRequest.addParam(token);
var imdbid = document.getElementById('imdbid').value || '';
var langcode = document.getElementById('langcode').value || 'all';
searchRequest.addParam([{imdbid: imdbid.replace('tt',''), season: '', episode: '', sublanguageid: langcode}]);
// Parse response
var results = searchRequest.send();
var xml = results.parseXML();
if (xml && xml.data) {
for (var i = 0; i < xml.data.length; i++) {
var downlink = xml.data[i].ZipDownloadLink;
var filename = xml.data[i].SubFileName;
document.getElementById('subtitles').innerHTML += "<a href="+downlink+">"+filename+"</a><br>";
}
} else {
console.debug('The xml object:', xml);
}
}
function search() {
try {
searchSubtitles();
} catch (e) {
document.getElementById('error').innerHTML = e.message || e;
}
}
</script>
</body>
</html>