<commit_before>// Configuration
var config = require('./config.js');

// DNS Server settings.
var dns = require('native-dns'),
    consts = require('native-dns-packet').consts,
    tld = require('tldjs'),
    async = require('async'),
    geoip = require('geoip');

var Record = '';

// GeoIP setup
var country = new geoip.Country(config.GeoDB),
    country_v6 = new geoip.Country6(config.GeoDB6),
    isp = new geoip.Org(config.GeoISP);
setInterval(function() {
    country.update(config.GeoDB);
    isp.update(config.GeoISP);
    // console.log('GeoIP Data updated.');
}, 86400000);

if (config.db === 'mongodb') {
    Record = require('./record-mysql.js');
} else if (config.db === 'mysql') {
    Record = require('./record-mysql.js');
} else {
    return console.log('Incorrect database selection.');
}

// Start servers
var UDPserver = dns.createServer({ dgram_type: 'udp4' });
UDPserver.serve(config.port);

// TCP server
if (config.enableTCP) {
    var TCPserver = dns.createTCPServer();
    if (config.enableV6) {
        TCPserver.serve(config.port, '::');
    } else {
        TCPserver.serve(config.port);
    }
}

// IPv6
if (config.enableV6) {
    var UDPserver6 = dns.createUDPServer({ dgram_type: 'udp6' });
    UDPserver6.serve(config.port);
}

console.log('DNS Server started at port ' + config.port + '.');

// Query events...
UDPserver.on('request', minimoedns);
UDPserver6.on('request', minimoedns);
TCPserver.on('request', minimoedns);

UDPserver.on('error', function (err, buff, req, res) {
    console.log('UDP Server ERR:\n');
    console.log(err);
});
UDPserver6.on('error', function(err, buff, req, res) {
    console.log('UDP6 Server ERR:\n');
    console.log(err);
});
TCPserver.on('error', function (err, buff, req, res) {
    console.log('TCP Server ERR:\n');
    console.log(err);
});

// Functions
function randomOrder() {
    return (Math.round(Math.random()) - 0.52);
}

function authorityNS(res, queryName, callback) {
    // Send authority NS records.
    config.nameservers.forEach(function(ns) {
        response.authority.push(dns.NS({
            name: queryName,
            data: ns,
            ttl: config.defaultTTL
        }));
    });
    callback();
}

function minimoedns(request, response) {
    // console.log(request);
    // console.log(JSON.stringify(request.edns_options[0].data));
    // console.log(request.edns_options[0].data.toJSON());

    var name = request.question[0].name,
        type = consts.qtypeToName(request.question[0].type),
        sourceIP = request.address.address;
    var tldname = tld.getDomain(name);

    // EDNS options
    if (request.edns_options[0]) {
        var tempip = request.edns_options[0].data.slice(4);
        sourceIP = tempip.toJSON().join('.');
        response.edns_options.push({
            code: 8,
            data: request.edns_options[0].data
        });
        response.additional.push({
            name: '',
            type: 41,
            rdlength: 8
        });
    }

    // Get source IP
    // console.log(sourceIP);
    var sourceDest = country.lookupSync(sourceIP),
        sourceISP = isp.lookupSync(sourceIP);
    if (!sourceDest) {
        sourceDest = country_v6.lookupSync(sourceIP)
    }
    // console.log(sourceDest);
    // console.log(sourceISP);
    console.log(sourceIP + ' requested ' + name);
    if (!tld.isValid(name)) {
        response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
        return response.send();
    }
    // return version if quested version.bind
    if (name === 'version.bind' && type === 'TXT') {
        response.answer.push(dns.TXT({
            name: 'version.bind',
            data: config.version || 'MiniMoeDNS',
            ttl: 5
        }));
        response.answer[0].class = 3;
        // console.log(response);
        return response.send();
    }

    Record.queryRecord(tldname, 'SOA', function(err, SOAresult) {
        if (err) {
            console.log(err);
        } else if (!SOAresult[0]) {
            response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
            response.send();
        } else {
            response.header.aa = 1;

            Record.queryGeo(name, type, sourceDest, sourceISP, function(err, georecords) {
                // console.log(georecords);
                if (err) {
                    console.log(err);
                }
                if (georecords[0]) {
                    // console.log(georecords);
                    // console.log('GeoDNS Record(s) found, sending optimized records...');
                    switch (georecords[0].type) {
                        case 'A':
                            georecords = georecords.sort(randomOrder);
                            georecords.forEach(function(record) {
                                response.answer.push(dns.A({
                                    name: record.name,
                                    address: record.content,
                                    ttl: record.ttl||config.defaultTTL
                                }));
                            });
                            break;
                        case 'AAAA':
                            georecords = georecords.sort(randomOrder);
                            georecords.forEach(function(record) {
                                response.answer.push(dns.AAAA({
                                    name: record.name,
                                    address: record.content,
                                    ttl: record.ttl||config.defaultTTL
                                }));
                            });
                            break;
                        case 'CNAME':
                            georecords.forEach(function(record) {
                                response.answer.push(dns.CNAME({
                                    name: record.name,
                                    data: record.content,
                                    ttl: record.ttl||config.defaultTTL
                                }));
                            });
                            break;
                    }
                        // Send authority NS records.
                    config.nameservers.forEach(function(ns) {
                        response.authority.push(dns.NS({
                            name: tldname,
                            data: ns,
                            ttl: config.defaultTTL
                        }));
                    });
                    return response.send();
                } else {
                    Record.queryRecord(name, type, function(err, records) {
                        // console.log('exec1');
                        // console.log(records);
                        if (err) {
                            console.log(err);
                        } else if (!records[0]) {
                            // Query if wildcard exists.
                            var sub = tld.getSubdomain(name),
                                pattern = new RegExp(/\./);
                            // console.log(sub);
                            if (sub == '') {
                                // directly send SOA as we queried before.
                                // push SOA to authority section
                                var content = SOAresult[0].content.split(" ");
                                response.authority.push(dns.SOA({
                                    name: SOAresult[0].name,
                                    primary: content[0],
                                    admin: content[1].replace("@", "."),
                                    serial: content[2],
                                    refresh: content[3],
                                    retry: content[4],
                                    expiration: content[5],
                                    minimum: content[6],
                                    ttl: SOAresult[0].ttl||config.defaultTTL
                                }));
                                response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                response.send();
                                /*
                                Record.queryRecord(name, 'SOA', function(err, doc) {
                                    // console.log('exec2');
                                    // console.log(doc);
                                    if (err) {
                                        console.log(err);
                                    }
                                    if (doc[0]) {
                                        var content = doc[0].content.split(" ");
                                        response.authority.push(dns.SOA({
                                            name: doc[0].name,
                                            primary: content[0],
                                            admin: content[1].replace("@", "."),
                                            serial: content[2],
                                            refresh: content[3],
                                            retry: content[4],
                                            expiration: content[5],
                                            minimum: content[6],
                                            ttl: doc[0].ttl||config.defaultTTL
                                        }));
                                        response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                        response.send();
                                    } else {
                                        // console.log('NXDOMAIN');
                                        response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                        response.send();
                                    }
                                });
                                */
                            } else  {
                                var queryName = name;
                                async.until(function() {
                                    return !tld.getSubdomain(queryName);
                                }, function(callback) {
                                    queryName = queryName.substr(queryName.indexOf('.') + 1);
                                    // console.log(queryName);
                                    Record.queryRecord('*.' + queryName, type, function(err, doc) {
                                        // console.log(doc)
                                        if (err) {
                                            console.log(err);
                                        }
                                        if (doc[0]) {
                                            // console.log(doc[0].type);
                                            switch (doc[0].type) {
                                                case 'A':
                                                    response.answer.push(dns.A({
                                                        name: name,
                                                        address: doc[0].content,
                                                        ttl: doc[0].ttl||config.defaultTTL
                                                    }));
                                                    break;
                                                case 'AAAA':
                                                    response.answer.push(dns.AAAA({
                                                        name: name,
                                                        address: doc[0].content,
                                                        ttl: doc[0].ttl||config.defaultTTL
                                                    }));
                                                    break;
                                                case 'CNAME':
                                                    response.answer.push(dns.CNAME({
                                                        name: name,
                                                        data: doc[0].content,
                                                        ttl: doc[0].ttl||config.defaultTTL
                                                    }));
                                                    break;
                                            }
                                            // Send authority NS records.
                                            config.nameservers.forEach(function(ns) {
                                                response.authority.push(dns.NS({
                                                    name: tldname,
                                                    data: ns,
                                                    ttl: config.defaultTTL
                                                }));
                                            });
                                            return response.send();
                                        }
                                        callback();
                                    });
                                }, function() {
                                    /*
                                    Record.queryRecord(tld.getDomain(name), 'SOA', function(err, doc) {
                                        if (err) {
                                            console.log(err);
                                        }
                                        if (doc[0]) {
                                            var content = doc[0].content.split(" ");
                                            response.authority.push(dns.SOA({
                                                name: doc[0].name,
                                                primary: content[0],
                                                admin: content[1].replace("@", "."),
                                                serial: content[2],
                                                refresh: content[3],
                                                retry: content[4],
                                                expiration: content[5],
                                                minimum: content[6],
                                                ttl: doc[0].ttl||config.defaultTTL
                                            }));
                                        }
                                        response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                        response.send();
                                    });
                                    */
                                    // push SOA to authority section
                                    var content = SOAresult[0].content.split(" ");
                                    response.authority.push(dns.SOA({
                                        name: SOAresult[0].name,
                                        primary: content[0],
                                        admin: content[1].replace("@", "."),
                                        serial: content[2],
                                        refresh: content[3],
                                        retry: content[4],
                                        expiration: content[5],
                                        minimum: content[6],
                                        ttl: SOAresult[0].ttl||config.defaultTTL
                                    }));
                                    response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                    response.send();
                                });
                            }

                        } else {
                            switch (records[0].type) {
                                case 'SOA':
                                    var content = records[0].content.split(" ");
                                    response.answer.push(dns.SOA({
                                        name: records[0].name,
                                        primary: content[0],
                                        admin: content[1].replace("@", "."),
                                        serial: content[2],
                                        refresh: content[3],
                                        retry: content[4],
                                        expiration: content[5],
                                        minimum: content[6],
                                        ttl: records[0].ttl||config.defaultTTL
                                    }));
                                    break;
                                case 'A':
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        response.answer.push(dns.A({
                                            name: record.name,
                                            address: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'AAAA':
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        response.answer.push(dns.AAAA({
                                            name: record.name,
                                            address: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'MX':
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        response.answer.push(dns.MX({
                                            name: record.name,
                                            priority: record.prio,
                                            exchange: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'TXT':
                                    records.forEach(function(record) {
                                        response.answer.push(dns.TXT({
                                            name: record.name,
                                            data: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'SRV':
                                    records.forEach(function(record) {
                                        var content = record.content.split(" ");
                                        response.answer.push(dns.SRV({
                                            name: record.name,
                                            priority: record.prio,
                                            weight: content[0],
                                            port: content[1],
                                            target: content[2],
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'NS':
                                    /*
                                     async.eachSeries(records, function(record, callback) {
                                     records.forEach(function(record) {
                                     var ns = config.nameservers.indexOf(record.content);
                                     if (ns > -1) {
                                     // we are authoerity server, sending additional information...
                                     response.additional.push(dns.A({
                                     name: record.content,
                                     address: config.nameserversIP[ns],
                                     ttl: config.defaultTTL
                                     }));
                                     }
                                     response.answer.push(dns.NS({
                                     name: record.name,
                                     data: record.content,
                                     ttl: record.ttl||config.defaultTTL
                                     }));
                                     });

                                     Record.queryRecord(record.content, 'A', function(err, docs) {
                                     // console.log(record.content);
                                     // console.log(docs);
                                     if (err) {
                                     console.log(err);
                                     }

                                     if (docs) {
                                     docs.forEach(function(doc) {
                                     console.log(doc);
                                     response.additional.push(dns.A({
                                     name: doc.name,
                                     address: doc.content,
                                     ttl: doc.ttl||config.defaultTTL
                                     }));
                                     });
                                     }

                                     callback();
                                     });
                                     }, function() {
                                     records.forEach(function(record) {
                                     response.answer.push(dns.NS({
                                     name: record.name,
                                     data: record.content,
                                     ttl: record.ttl||config.defaultTTL
                                     }));
                                     });
                                     });
                                     */
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        var ns = config.nameservers.indexOf(record.content);
                                        if (ns > -1) {
                                            // we are authoerity server, sending additional information...
                                            response.additional.push(dns.A({
                                                name: record.content,
                                                address: config.nameserversIP[ns],
                                                ttl: config.defaultTTL
                                            }));
                                        }
                                        response.answer.push(dns.NS({
                                            name: record.name,
                                            data: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });

                                    return response.send();
                                case 'CNAME':
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        response.answer.push(dns.CNAME({
                                            name: record.name,
                                            data: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));

                                    });
                                    break;
                            }

                            // Send authority NS records.
                            config.nameservers.forEach(function(ns) {
                                response.authority.push(dns.NS({
                                    name: tldname,
                                    data: ns,
                                    ttl: config.defaultTTL
                                }));
                            });
                            response.send();
                            console.log(response);
                        }
                    });
                }
            });
        }
    });
}
<commit_msg>Add additonal A records for NS records<commit_after>// Configuration
var config = require('./config.js');

// DNS Server settings.
var dns = require('native-dns'),
    consts = require('native-dns-packet').consts,
    tld = require('tldjs'),
    async = require('async'),
    geoip = require('geoip');

var Record = '';

// GeoIP setup
var country = new geoip.Country(config.GeoDB),
    country_v6 = new geoip.Country6(config.GeoDB6),
    isp = new geoip.Org(config.GeoISP);
setInterval(function() {
    country.update(config.GeoDB);
    isp.update(config.GeoISP);
    // console.log('GeoIP Data updated.');
}, 86400000);

if (config.db === 'mongodb') {
    Record = require('./record-mysql.js');
} else if (config.db === 'mysql') {
    Record = require('./record-mysql.js');
} else {
    return console.log('Incorrect database selection.');
}

// Start servers
var UDPserver = dns.createServer({ dgram_type: 'udp4' });
UDPserver.serve(config.port);

// TCP server
if (config.enableTCP) {
    var TCPserver = dns.createTCPServer();
    if (config.enableV6) {
        TCPserver.serve(config.port, '::');
    } else {
        TCPserver.serve(config.port);
    }
}

// IPv6
if (config.enableV6) {
    var UDPserver6 = dns.createUDPServer({ dgram_type: 'udp6' });
    UDPserver6.serve(config.port);
}

console.log('DNS Server started at port ' + config.port + '.');

// Query events...
UDPserver.on('request', minimoedns);
UDPserver6.on('request', minimoedns);
TCPserver.on('request', minimoedns);

UDPserver.on('error', function (err, buff, req, res) {
    console.log('UDP Server ERR:\n');
    console.log(err);
});
UDPserver6.on('error', function(err, buff, req, res) {
    console.log('UDP6 Server ERR:\n');
    console.log(err);
});
TCPserver.on('error', function (err, buff, req, res) {
    console.log('TCP Server ERR:\n');
    console.log(err);
});

// Functions
function randomOrder() {
    return (Math.round(Math.random()) - 0.52);
}

function authorityNS(res, queryName, callback) {
    // Send authority NS records.
    config.nameservers.forEach(function(ns) {
        response.authority.push(dns.NS({
            name: queryName,
            data: ns,
            ttl: config.defaultTTL
        }));
    });
    callback();
}

function minimoedns(request, response) {
    // console.log(request);
    // console.log(JSON.stringify(request.edns_options[0].data));
    // console.log(request.edns_options[0].data.toJSON());

    var name = request.question[0].name,
        type = consts.qtypeToName(request.question[0].type),
        sourceIP = request.address.address;
    var tldname = tld.getDomain(name);

    // EDNS options
    if (request.edns_options[0]) {
        var tempip = request.edns_options[0].data.slice(4);
        sourceIP = tempip.toJSON().join('.');
        response.edns_options.push({
            code: 8,
            data: request.edns_options[0].data
        });
        response.additional.push({
            name: '',
            type: 41,
            rdlength: 8
        });
    }

    // Get source IP
    // console.log(sourceIP);
    var sourceDest = country.lookupSync(sourceIP),
        sourceISP = isp.lookupSync(sourceIP);
    if (!sourceDest) {
        sourceDest = country_v6.lookupSync(sourceIP)
    }
    // console.log(sourceDest);
    // console.log(sourceISP);
    console.log(sourceIP + ' requested ' + name);
    if (!tld.isValid(name)) {
        response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
        return response.send();
    }
    // return version if quested version.bind
    if (name === 'version.bind' && type === 'TXT') {
        response.answer.push(dns.TXT({
            name: 'version.bind',
            data: config.version || 'MiniMoeDNS',
            ttl: 5
        }));
        response.answer[0].class = 3;
        // console.log(response);
        return response.send();
    }

    Record.queryRecord(tldname, 'SOA', function(err, SOAresult) {
        if (err) {
            console.log(err);
        } else if (!SOAresult[0]) {
            response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
            response.send();
        } else {
            response.header.aa = 1;

            Record.queryGeo(name, type, sourceDest, sourceISP, function(err, georecords) {
                // console.log(georecords);
                if (err) {
                    console.log(err);
                }
                if (georecords[0]) {
                    // console.log(georecords);
                    // console.log('GeoDNS Record(s) found, sending optimized records...');
                    switch (georecords[0].type) {
                        case 'A':
                            georecords = georecords.sort(randomOrder);
                            georecords.forEach(function(record) {
                                response.answer.push(dns.A({
                                    name: record.name,
                                    address: record.content,
                                    ttl: record.ttl||config.defaultTTL
                                }));
                            });
                            break;
                        case 'AAAA':
                            georecords = georecords.sort(randomOrder);
                            georecords.forEach(function(record) {
                                response.answer.push(dns.AAAA({
                                    name: record.name,
                                    address: record.content,
                                    ttl: record.ttl||config.defaultTTL
                                }));
                            });
                            break;
                        case 'CNAME':
                            georecords.forEach(function(record) {
                                response.answer.push(dns.CNAME({
                                    name: record.name,
                                    data: record.content,
                                    ttl: record.ttl||config.defaultTTL
                                }));
                            });
                            break;
                    }
                    // Send authority NS records.
                    config.nameservers.forEach(function(ns) {
                        response.authority.push(dns.NS({
                            name: tldname,
                            data: ns,
                            ttl: config.defaultTTL
                        }));
                        response.additional.push(dns.A({
                            name: ns,
                            address: config.nameserversIP[config.nameservers.indexOf(ns)],
                            ttl: config.defaultTTL
                        }));
                    });
                    return response.send();
                } else {
                    Record.queryRecord(name, type, function(err, records) {
                        // console.log('exec1');
                        // console.log(records);
                        if (err) {
                            console.log(err);
                        } else if (!records[0]) {
                            // Query if wildcard exists.
                            var sub = tld.getSubdomain(name),
                                pattern = new RegExp(/\./);
                            // console.log(sub);
                            if (sub == '') {
                                // directly send SOA as we queried before.
                                // push SOA to authority section
                                var content = SOAresult[0].content.split(" ");
                                response.authority.push(dns.SOA({
                                    name: SOAresult[0].name,
                                    primary: content[0],
                                    admin: content[1].replace("@", "."),
                                    serial: content[2],
                                    refresh: content[3],
                                    retry: content[4],
                                    expiration: content[5],
                                    minimum: content[6],
                                    ttl: SOAresult[0].ttl||config.defaultTTL
                                }));
                                response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                response.send();
                                /*
                                Record.queryRecord(name, 'SOA', function(err, doc) {
                                    // console.log('exec2');
                                    // console.log(doc);
                                    if (err) {
                                        console.log(err);
                                    }
                                    if (doc[0]) {
                                        var content = doc[0].content.split(" ");
                                        response.authority.push(dns.SOA({
                                            name: doc[0].name,
                                            primary: content[0],
                                            admin: content[1].replace("@", "."),
                                            serial: content[2],
                                            refresh: content[3],
                                            retry: content[4],
                                            expiration: content[5],
                                            minimum: content[6],
                                            ttl: doc[0].ttl||config.defaultTTL
                                        }));
                                        response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                        response.send();
                                    } else {
                                        // console.log('NXDOMAIN');
                                        response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                        response.send();
                                    }
                                });
                                */
                            } else  {
                                var queryName = name;
                                async.until(function() {
                                    return !tld.getSubdomain(queryName);
                                }, function(callback) {
                                    queryName = queryName.substr(queryName.indexOf('.') + 1);
                                    // console.log(queryName);
                                    Record.queryRecord('*.' + queryName, type, function(err, doc) {
                                        // console.log(doc)
                                        if (err) {
                                            console.log(err);
                                        }
                                        if (doc[0]) {
                                            // console.log(doc[0].type);
                                            switch (doc[0].type) {
                                                case 'A':
                                                    response.answer.push(dns.A({
                                                        name: name,
                                                        address: doc[0].content,
                                                        ttl: doc[0].ttl||config.defaultTTL
                                                    }));
                                                    break;
                                                case 'AAAA':
                                                    response.answer.push(dns.AAAA({
                                                        name: name,
                                                        address: doc[0].content,
                                                        ttl: doc[0].ttl||config.defaultTTL
                                                    }));
                                                    break;
                                                case 'CNAME':
                                                    response.answer.push(dns.CNAME({
                                                        name: name,
                                                        data: doc[0].content,
                                                        ttl: doc[0].ttl||config.defaultTTL
                                                    }));
                                                    break;
                                            }
                                            // Send authority NS records.
                                            config.nameservers.forEach(function(ns) {
                                                response.authority.push(dns.NS({
                                                    name: tldname,
                                                    data: ns,
                                                    ttl: config.defaultTTL
                                                }));
                                            });
                                            return response.send();
                                        }
                                        callback();
                                    });
                                }, function() {
                                    /*
                                    Record.queryRecord(tld.getDomain(name), 'SOA', function(err, doc) {
                                        if (err) {
                                            console.log(err);
                                        }
                                        if (doc[0]) {
                                            var content = doc[0].content.split(" ");
                                            response.authority.push(dns.SOA({
                                                name: doc[0].name,
                                                primary: content[0],
                                                admin: content[1].replace("@", "."),
                                                serial: content[2],
                                                refresh: content[3],
                                                retry: content[4],
                                                expiration: content[5],
                                                minimum: content[6],
                                                ttl: doc[0].ttl||config.defaultTTL
                                            }));
                                        }
                                        response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                        response.send();
                                    });
                                    */
                                    // push SOA to authority section
                                    var content = SOAresult[0].content.split(" ");
                                    response.authority.push(dns.SOA({
                                        name: SOAresult[0].name,
                                        primary: content[0],
                                        admin: content[1].replace("@", "."),
                                        serial: content[2],
                                        refresh: content[3],
                                        retry: content[4],
                                        expiration: content[5],
                                        minimum: content[6],
                                        ttl: SOAresult[0].ttl||config.defaultTTL
                                    }));
                                    response.header.rcode = consts.NAME_TO_RCODE.NOTFOUND;
                                    response.send();
                                });
                            }

                        } else {
                            switch (records[0].type) {
                                case 'SOA':
                                    var content = records[0].content.split(" ");
                                    response.answer.push(dns.SOA({
                                        name: records[0].name,
                                        primary: content[0],
                                        admin: content[1].replace("@", "."),
                                        serial: content[2],
                                        refresh: content[3],
                                        retry: content[4],
                                        expiration: content[5],
                                        minimum: content[6],
                                        ttl: records[0].ttl||config.defaultTTL
                                    }));
                                    break;
                                case 'A':
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        response.answer.push(dns.A({
                                            name: record.name,
                                            address: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'AAAA':
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        response.answer.push(dns.AAAA({
                                            name: record.name,
                                            address: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'MX':
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        response.answer.push(dns.MX({
                                            name: record.name,
                                            priority: record.prio,
                                            exchange: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'TXT':
                                    records.forEach(function(record) {
                                        response.answer.push(dns.TXT({
                                            name: record.name,
                                            data: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'SRV':
                                    records.forEach(function(record) {
                                        var content = record.content.split(" ");
                                        response.answer.push(dns.SRV({
                                            name: record.name,
                                            priority: record.prio,
                                            weight: content[0],
                                            port: content[1],
                                            target: content[2],
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });
                                    break;
                                case 'NS':
                                    /*
                                     async.eachSeries(records, function(record, callback) {
                                     records.forEach(function(record) {
                                     var ns = config.nameservers.indexOf(record.content);
                                     if (ns > -1) {
                                     // we are authoerity server, sending additional information...
                                     response.additional.push(dns.A({
                                     name: record.content,
                                     address: config.nameserversIP[ns],
                                     ttl: config.defaultTTL
                                     }));
                                     }
                                     response.answer.push(dns.NS({
                                     name: record.name,
                                     data: record.content,
                                     ttl: record.ttl||config.defaultTTL
                                     }));
                                     });

                                     Record.queryRecord(record.content, 'A', function(err, docs) {
                                     // console.log(record.content);
                                     // console.log(docs);
                                     if (err) {
                                     console.log(err);
                                     }

                                     if (docs) {
                                     docs.forEach(function(doc) {
                                     console.log(doc);
                                     response.additional.push(dns.A({
                                     name: doc.name,
                                     address: doc.content,
                                     ttl: doc.ttl||config.defaultTTL
                                     }));
                                     });
                                     }

                                     callback();
                                     });
                                     }, function() {
                                     records.forEach(function(record) {
                                     response.answer.push(dns.NS({
                                     name: record.name,
                                     data: record.content,
                                     ttl: record.ttl||config.defaultTTL
                                     }));
                                     });
                                     });
                                     */
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        var ns = config.nameservers.indexOf(record.content);
                                        if (ns > -1) {
                                            // we are authoerity server, sending additional information...
                                            response.additional.push(dns.A({
                                                name: record.content,
                                                address: config.nameserversIP[ns],
                                                ttl: config.defaultTTL
                                            }));
                                        }
                                        response.answer.push(dns.NS({
                                            name: record.name,
                                            data: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));
                                    });

                                    return response.send();
                                case 'CNAME':
                                    records = records.sort(randomOrder);
                                    records.forEach(function(record) {
                                        response.answer.push(dns.CNAME({
                                            name: record.name,
                                            data: record.content,
                                            ttl: record.ttl||config.defaultTTL
                                        }));

                                    });
                                    break;
                            }

                            // Send authority NS records.
                            config.nameservers.forEach(function(ns) {
                                response.authority.push(dns.NS({
                                    name: tldname,
                                    data: ns,
                                    ttl: config.defaultTTL
                                }));
                                response.additional.push(dns.A({
                                    name: ns,
                                    address: config.nameserversIP[config.nameservers.indexOf(ns)],
                                    ttl: config.defaultTTL
                                }));
                            });
                            response.send();
                            // console.log(response);
                        }
                    });
                }
            });
        }
    });
}
<|endoftext|>


<commit_before>
var SC=µ.shortcut({
	File:"File"
});
var LOG=require("./logger");
var logger;

var rest={};

worker.init=function(param)
{
	logger=LOG.setCoreLogger(LOG("apps/"+param.name));

	return new SC.File("rest").listFiles().then(files=>
		files.filter(s=>s.slice(-3)===".js").map(s=>s.slice(0,-3))//cut ".js"
		.forEach(script=>
			rest[script]=require(new SC.File("./rest").changePath(script).getAbsolutePath())
		)
	);
}
worker.restCall=function(param)
{
	if(param.path.length==0)
	{
		return JSON.stringify(rest,function(key,value)
		{
			if(value instanceof Function) return value.toString().match(/^[^\(]+[^,]*,?([^\)]*)/)[1].split(",").map(s=>s.trim()).filter(s=>s!="");
			return value;
		});
	}
	else
	{
		var method=rest;
		var i=0;
		while(method&&typeof method!="function")
		{
			method=method[param.path[i++]];
		}
		if(!method)
		{
			return Promise.reject({data:"no such method: "+param.path.slice(0,i)});
		}
		else
		{
			param.path=param.path.slice(i);
			param.headers=null;
			param.status=null;
			return Promise.resolve(method.apply(null,[param,param.path]))
			.then(data=>({data:data,headers:param.headers,status:param.status}),
			error=>Promise.reject({data:error,headers:param.headers,status:param.status}));
		}
	}
};<commit_msg>handle not existing rest folders<commit_after>
var SC=µ.shortcut({
	File:"File"
});
var LOG=require("./logger");
var logger;

var rest={};

worker.init=function(param)
{
	logger=LOG.setCoreLogger(LOG("apps/"+param.name));

	return new SC.File("rest").exists()
	.then(function()
	{
		return this.listFiles().then(files=>
			files.filter(s=>s.slice(-3)===".js").map(s=>s.slice(0,-3))//cut ".js"
			.forEach(script=>
				rest[script]=require(new SC.File("./rest").changePath(script).getAbsolutePath())
			)
		);
	},
	function()
	{
		return "'rest' folder does not exist";
	})
	.catch(e=>
	{
		setTimeout(function(){throw e;},1) //for native error message
		return Promise.reject(LOG.errorSerializer(e));
	});
}
worker.restCall=function(param)
{
	if(param.path.length==0)
	{
		return JSON.stringify(rest,function(key,value)
		{
			if(value instanceof Function) return value.toString().match(/^[^\(]+[^,]*,?([^\)]*)/)[1].split(",").map(s=>s.trim()).filter(s=>s!="");
			return value;
		});
	}
	else
	{
		var method=rest;
		var i=0;
		while(method&&typeof method!="function")
		{
			method=method[param.path[i++]];
		}
		if(!method)
		{
			return Promise.reject({data:"no such method: "+param.path.slice(0,i)});
		}
		else
		{
			param.path=param.path.slice(i);
			param.headers=null;
			param.status=null;
			return Promise.resolve(method.apply(null,[param,param.path]))
			.then(data=>({data:data,headers:param.headers,status:param.status}),
			error=>Promise.reject({data:error,headers:param.headers,status:param.status}));
		}
	}
};<|endoftext|>


<commit_before>// ==UserScript==
// @name         Nihonduo
// @namespace    none
// @version      0.1
// @description  Duolingo reverse japanese tree helper
// @author       t17dr
// @homepage     http://www.t17dr.com
// @include      https://www.duolingo.com/*
// @require      http://code.jquery.com/jquery-latest.js
// @require      https://rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // An array to memoize API requests for furigana
    var furiganaCache = [];

    // Text to speech backlog
    var waitingSentence = "";

    // Are the text to speech voices ready?
    var voicesLoaded = false;

    // Can we play text to speech?
    var canRead = true;

    // Is the API key prompt visible?
    var apiDialogVisible = false;

    // The hiragana API key
    var key = "";

    // Show a prompt to enter API key, call callback when the user clicks OK and pass the key as argument
    function showAPIkeyDialog(callback)
    {
        key = localStorage.getItem("nihonduo-api-key");
        var containerExists = $("#session-element-container").length > 0;
        var canDisplay = !apiDialogVisible && containerExists;

        if ( (key === null || key === "null") && canDisplay)
        {

            // Big HTML blob to show the prompt
            $("#session-element-container").append(`

Please paste your API key here, you will get the key at
            https://labs.goo.ne.jp/en/apiregister/, you'll need a
            github account.

            <input type=\"text\" value=\"\" name=\"key\" id=\"nihonduo-key\" class=\"text ui-widget-content ui-corner-all\" style=\"width: 100%;\" placeholder=\"Please enter your API key here\">

`);

            apiDialogVisible = true;

            $("#api-prompt").append("
<button type=\"button\" id=\"nihonduo-api-ok\">OK</button><button type=\"button\" id=\"nihonduo-api-cancel\">Cancel</button>
");
            $("#api-prompt").prepend("
Nihonduo userscript
");
        }

        $("#nihonduo-api-cancel").unbind();
        $("#nihonduo-api-cancel").bind("click", function(){
            $("#api-prompt").hide(400);
        });

        $("#nihonduo-api-ok").unbind();
        $("#nihonduo-api-ok").bind("click", function(){
            var k = $("#nihonduo-key").val();

            // Use HTML5 local storage, GM_setValue just causes a lot of trouble with the sandbox
            localStorage.setItem('nihonduo-api-key', k);
            $("#api-prompt").hide(400);
            callback(k);
        });

    }

    // Is the current page in the japanese reverse tree?
    function isJapaneseReverseTree()
    {
        if (window.duo === undefined)
        {
            return false;
        }
        var langFrom = window.duo.user.attributes.ui_language.indexOf("ja") !== -1;
        var langTo = window.duo.user.attributes.learning_language.indexOf("en") !== -1;

        return langFrom && langTo;
    }

    // Debug helper, prints duo user attributes to console
    function printDuoAttr()
    {
        var k = Object.keys(window.duo.user.attributes);
        for (var key of k)
        {
            console.log(key);
        }
    }

    // When new voices are loaded, look if there is a sentence in backlog to read
    var isFF = navigator.userAgent.search("Firefox") > -1;
    if (!isFF)                                                      // This is not supported on Firefox
    {
        window.speechSynthesis.onvoiceschanged = function() {
            voicesLoaded = true;
            if (waitingSentence !== "")
            {
                readJapanese(waitingSentence);
                waitingSentence = "";
            }
        };
    }

    $(document).bind('keydown', 'ctrl+space', readChallenge);

    // Use text to speech to read a japanese sentence
    function readJapanese(sentence)
    {
        if (!voicesLoaded)      // voices not yet loaded, add to backlog
        {
            waitingSentence = sentence;
        } else if (canRead) {
            var synth = window.speechSynthesis;

            var msg = new SpeechSynthesisUtterance();
            msg.voice = getJapaneseVoice();
            msg.voiceURI = 'native';
            msg.volume = 1; // 0 to 1
            msg.rate = 1; // 0.1 to 10
            msg.pitch = 1; //0 to 2
            msg.text = sentence;
            msg.lang = 'ja-JP';

            window.speechSynthesis.speak(msg);

            canRead = false;
            setTimeout(function(){ canRead=true; }, 2000);
        }
    }

    // Find a japanese language text to speech voice
    function getJapaneseVoice()
    {
        var synth = window.speechSynthesis;
        var voices = synth.getVoices();

        var voiceID = 0;
        for (var i = 0; i < voices.length; i++)
        {
             if (voices[i].lang === "ja-JP")
             {
                 voiceID = i;
                 break;
             }
        }

        return voices[voiceID];
    }

    // Get a list of the hint elements
    function getHints(sentence)
    {
        var hints;

        if (!isJapanese(sentence))
        {
            hints = $(".hint-table .inner .content table tbody tr td");
        } else {
            hints = $(".hint-table .inner .content table thead tr");
        }

        return hints;
    }

    // Append hiragana pronounciation to hint tables
    function furigana()
    {
        var sentence = getCurrentSentence();
        var headerString = "";

        var hints = getHints(sentence);

        hints.each(function(){
            var current = $(this);
            var hint = "";

            if (!isJapanese(sentence))
            {
                hint = (current.text().split("|"))[0];
            } else {

                var parentTr = $(this);
                headerString = parentTr.text();
                hint = (headerString.split("|"))[0];
                var th = parentTr.find("> th");
                th.remove();
                var elm = $("" + headerString + "	");
                parentTr.html(elm);
                current = parentTr.find("> th");

            }

            if (hint in furiganaCache)
            {
                //console.log("cache");
                applyFurigana(furiganaCache[hint], current, hint);
            }
            else
            {
                //console.log("non-cache");
                furiganaAPIrequest(current, hint);
            }

        });

    }

    // Add a link to jisho dictionary for each hint
    function jisho()
    {
        var sentence = getCurrentSentence();
        var headerString = "";

        var hints = getHints(sentence);

        hints.each(function(){
            var current = $(this);
            var hint = "";
            var canAppend = true;

            if (!isJapanese(sentence))
            {
                hint = (current.text().split("|"))[0];
                current = $(this);
            } else {
                var parentTr = $(this);
                headerString = parentTr.text();
                hint = (headerString.split("|"))[0];
                current = parentTr.find("> th");

                if (headerString.indexOf("|") == -1)
                {
                    canAppend = false;
                }

            }

            if (current.find("#nihonduo-jisho").length > 0)
            {
                canAppend = false;
            }

            if (canAppend)
            {
                current.append(" Jisho");

                var jisho = current.find("#nihonduo-jisho");
                jisho.unbind();
                jisho.bind("click", function() {
                    window.open("http://jisho.org/search/" + hint.trim(), "_blank");
                });
            }
        });
    }

    // Make a request to the labs.goo.ne.jp API to get hiragana pronounciation, append result to cache
    function furiganaAPIrequest(current, hint)
    {
        if (hint === "" || hint === undefined)
        {
            return;
        }

        // Try to get the API key
        showAPIkeyDialog(function(k){
            key = k;
        });

        if (key != "" && key != null && key != "null")
        {
            $.ajax({
                method: "POST",
                url: "https://labs.goo.ne.jp/api/hiragana",
                dataType: "json",
                data: { app_id: key,
                        sentence: hint,
                        output_type: "hiragana" }
            })
                .done(function( data ) {
                    observer.disconnect();
                    furiganaCache[hint] = data;
                    applyFurigana(data, current, hint);
                    observer.observe(target, config);
            })
                .fail(function(){
                    $(".hint-table .inner .content table tbody tr").append("(Nihonduo hiragana failed to load)	");
                });
        }
    }

    // Append furigana to hints table element
    // data: API request result
    // parentElement: DOM element to append to (td for EN->JP, th for JP->EN challenges)
    // originalText: element content before appending
    function applyFurigana(data, parentElement, originalText)
    {
        var original = originalText.split("|");

        parentElement.html(original[0] + "| " + data.converted + "");

        // Apply jisho links AFTER furigana
        jisho();
    }

    // Does text include any japanese characters?
    function isJapanese(text)
    {
        return /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/.test(text);
    }

    // Get current challenge sentence from the DOM
    function getCurrentSentence()
    {
        var sentence = "";

        $(".text-to-translate").find(".non-space.token").each(function(){
            sentence += $(this).text();
        });

        return sentence;
    }

    // Text to speech read the current challenge, if the original is in japanese
    function readChallenge()
    {
        var sentence = getCurrentSentence();
        if (isJapanese(sentence))
        {
            sentence = sentence.replace(/[a-zA-Zé\?\!\.\,\'\;\:]*/, "");
            readJapanese(sentence);
        }
    }

    function appendSpeakIcon()
    {
        var sentence = getCurrentSentence();
        var alreadyExists = $("#speak-japanese").length > 0;

        if (isJapanese(sentence) && !alreadyExists)
        {
            $(".text-to-translate").prepend("");
            $("#speak-japanese").off("click");
            $("#speak-japanese").on("click", readChallenge);
        }
    }

    // Should TTS play on this change?
    function shouldSpeakOnChange(mutations)
    {

        for (var mutation of mutations)
        {

            if (mutation.target.nodeType == 1) // ELEMENT_NODE
            {
                if (mutation.target.id.indexOf("submitted-text") !== -1 ||
                    mutation.target.id.indexOf("big-speaker") !== -1 ||
                    mutation.target.id.indexOf("prev-session-element-container") !== -1 )
                {
                    return false;
                }
            }
        }

        if (!isJapanese(getCurrentSentence()))
        {
            return false;
        }

        return true;
    }

    var observer = new MutationObserver(function(mutations) {
        observer.disconnect();

            // rebind hotkeys for textarea if it already exists
            $(".textarea-translate").unbind("keydown", readChallenge);
            $(".textarea-translate").bind('keydown', 'ctrl+space', readChallenge);

            furigana();

            if (shouldSpeakOnChange(mutations))
            {
                readChallenge();
            }
            appendSpeakIcon();

        observer.observe(target, config);
    });

    var config = {
        childList: true,
        characterData: true,
        subtree: true
    };

    var target;

    // the main mutation observer watches only main player div, do so only if the element actually exists
    var body = $("body")[0];
    var bodyObserver = new MutationObserver(function(mutations) {
        bodyObserver.disconnect();
            if ( $(".player-main").length )     // does player exist yet?
            {
                if (isJapaneseReverseTree())
                {
                    // MutationObserver target
                    target = $(".player-main")[0];
                    observer.observe(target, config);
                }
            } else {
                observer.disconnect();
            }
        bodyObserver.observe(body, config);
    });

    bodyObserver.observe(body, config);

})();<commit_msg>Update URLs<commit_after>// ==UserScript==
// @name         Nihonduo
// @namespace    none
// @version      0.1
// @description  Duolingo reverse japanese tree helper
// @author       t17dr
// @homepage     http://www.t17dr.com
// @include      https://www.duolingo.com/*
// @require      http://code.jquery.com/jquery-latest.js
// @require      https://rawgit.com/jeresig/jquery.hotkeys/master/jquery.hotkeys.js
// @downloadURL  https://github.com/t17dr/nihonduo/raw/master/Nihonduo.user.js
// @updateURL    https://github.com/t17dr/nihonduo/raw/master/Nihonduo.user.js
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // An array to memoize API requests for furigana
    var furiganaCache = [];

    // Text to speech backlog
    var waitingSentence = "";

    // Are the text to speech voices ready?
    var voicesLoaded = false;

    // Can we play text to speech?
    var canRead = true;

    // Is the API key prompt visible?
    var apiDialogVisible = false;

    // The hiragana API key
    var key = "";

    // Show a prompt to enter API key, call callback when the user clicks OK and pass the key as argument
    function showAPIkeyDialog(callback)
    {
        key = localStorage.getItem("nihonduo-api-key");
        var containerExists = $("#session-element-container").length > 0;
        var canDisplay = !apiDialogVisible && containerExists;

        if ( (key === null || key === "null") && canDisplay)
        {

            // Big HTML blob to show the prompt
            $("#session-element-container").append(`

Please paste your API key here, you will get the key at
            https://labs.goo.ne.jp/en/apiregister/, you'll need a
            github account.

            <input type=\"text\" value=\"\" name=\"key\" id=\"nihonduo-key\" class=\"text ui-widget-content ui-corner-all\" style=\"width: 100%;\" placeholder=\"Please enter your API key here\">

`);

            apiDialogVisible = true;

            $("#api-prompt").append("
<button type=\"button\" id=\"nihonduo-api-ok\">OK</button><button type=\"button\" id=\"nihonduo-api-cancel\">Cancel</button>
");
            $("#api-prompt").prepend("
Nihonduo userscript
");
        }

        $("#nihonduo-api-cancel").unbind();
        $("#nihonduo-api-cancel").bind("click", function(){
            $("#api-prompt").hide(400);
        });

        $("#nihonduo-api-ok").unbind();
        $("#nihonduo-api-ok").bind("click", function(){
            var k = $("#nihonduo-key").val();

            // Use HTML5 local storage, GM_setValue just causes a lot of trouble with the sandbox
            localStorage.setItem('nihonduo-api-key', k);
            $("#api-prompt").hide(400);
            callback(k);
        });

    }

    // Is the current page in the japanese reverse tree?
    function isJapaneseReverseTree()
    {
        if (window.duo === undefined)
        {
            return false;
        }
        var langFrom = window.duo.user.attributes.ui_language.indexOf("ja") !== -1;
        var langTo = window.duo.user.attributes.learning_language.indexOf("en") !== -1;

        return langFrom && langTo;
    }

    // Debug helper, prints duo user attributes to console
    function printDuoAttr()
    {
        var k = Object.keys(window.duo.user.attributes);
        for (var key of k)
        {
            console.log(key);
        }
    }

    // When new voices are loaded, look if there is a sentence in backlog to read
    var isFF = navigator.userAgent.search("Firefox") > -1;
    if (!isFF)                                                      // This is not supported on Firefox
    {
        window.speechSynthesis.onvoiceschanged = function() {
            voicesLoaded = true;
            if (waitingSentence !== "")
            {
                readJapanese(waitingSentence);
                waitingSentence = "";
            }
        };
    }

    $(document).bind('keydown', 'ctrl+space', readChallenge);

    // Use text to speech to read a japanese sentence
    function readJapanese(sentence)
    {
        if (!voicesLoaded)      // voices not yet loaded, add to backlog
        {
            waitingSentence = sentence;
        } else if (canRead) {
            var synth = window.speechSynthesis;

            var msg = new SpeechSynthesisUtterance();
            msg.voice = getJapaneseVoice();
            msg.voiceURI = 'native';
            msg.volume = 1; // 0 to 1
            msg.rate = 1; // 0.1 to 10
            msg.pitch = 1; //0 to 2
            msg.text = sentence;
            msg.lang = 'ja-JP';

            window.speechSynthesis.speak(msg);

            canRead = false;
            setTimeout(function(){ canRead=true; }, 2000);
        }
    }

    // Find a japanese language text to speech voice
    function getJapaneseVoice()
    {
        var synth = window.speechSynthesis;
        var voices = synth.getVoices();

        var voiceID = 0;
        for (var i = 0; i < voices.length; i++)
        {
             if (voices[i].lang === "ja-JP")
             {
                 voiceID = i;
                 break;
             }
        }

        return voices[voiceID];
    }

    // Get a list of the hint elements
    function getHints(sentence)
    {
        var hints;

        if (!isJapanese(sentence))
        {
            hints = $(".hint-table .inner .content table tbody tr td");
        } else {
            hints = $(".hint-table .inner .content table thead tr");
        }

        return hints;
    }

    // Append hiragana pronounciation to hint tables
    function furigana()
    {
        var sentence = getCurrentSentence();
        var headerString = "";

        var hints = getHints(sentence);

        hints.each(function(){
            var current = $(this);
            var hint = "";

            if (!isJapanese(sentence))
            {
                hint = (current.text().split("|"))[0];
            } else {

                var parentTr = $(this);
                headerString = parentTr.text();
                hint = (headerString.split("|"))[0];
                var th = parentTr.find("> th");
                th.remove();
                var elm = $("" + headerString + "	");
                parentTr.html(elm);
                current = parentTr.find("> th");

            }

            if (hint in furiganaCache)
            {
                //console.log("cache");
                applyFurigana(furiganaCache[hint], current, hint);
            }
            else
            {
                //console.log("non-cache");
                furiganaAPIrequest(current, hint);
            }

        });

    }

    // Add a link to jisho dictionary for each hint
    function jisho()
    {
        var sentence = getCurrentSentence();
        var headerString = "";

        var hints = getHints(sentence);

        hints.each(function(){
            var current = $(this);
            var hint = "";
            var canAppend = true;

            if (!isJapanese(sentence))
            {
                hint = (current.text().split("|"))[0];
                current = $(this);
            } else {
                var parentTr = $(this);
                headerString = parentTr.text();
                hint = (headerString.split("|"))[0];
                current = parentTr.find("> th");

                if (headerString.indexOf("|") == -1)
                {
                    canAppend = false;
                }

            }

            if (current.find("#nihonduo-jisho").length > 0)
            {
                canAppend = false;
            }

            if (canAppend)
            {
                current.append(" Jisho");

                var jisho = current.find("#nihonduo-jisho");
                jisho.unbind();
                jisho.bind("click", function() {
                    window.open("http://jisho.org/search/" + hint.trim(), "_blank");
                });
            }
        });
    }

    // Make a request to the labs.goo.ne.jp API to get hiragana pronounciation, append result to cache
    function furiganaAPIrequest(current, hint)
    {
        if (hint === "" || hint === undefined)
        {
            return;
        }

        // Try to get the API key
        showAPIkeyDialog(function(k){
            key = k;
        });

        if (key != "" && key != null && key != "null")
        {
            $.ajax({
                method: "POST",
                url: "https://labs.goo.ne.jp/api/hiragana",
                dataType: "json",
                data: { app_id: key,
                        sentence: hint,
                        output_type: "hiragana" }
            })
                .done(function( data ) {
                    observer.disconnect();
                    furiganaCache[hint] = data;
                    applyFurigana(data, current, hint);
                    observer.observe(target, config);
            })
                .fail(function(){
                    $(".hint-table .inner .content table tbody tr").append("(Nihonduo hiragana failed to load)	");
                });
        }
    }

    // Append furigana to hints table element
    // data: API request result
    // parentElement: DOM element to append to (td for EN->JP, th for JP->EN challenges)
    // originalText: element content before appending
    function applyFurigana(data, parentElement, originalText)
    {
        var original = originalText.split("|");

        parentElement.html(original[0] + "| " + data.converted + "");

        // Apply jisho links AFTER furigana
        jisho();
    }

    // Does text include any japanese characters?
    function isJapanese(text)
    {
        return /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/.test(text);
    }

    // Get current challenge sentence from the DOM
    function getCurrentSentence()
    {
        var sentence = "";

        $(".text-to-translate").find(".non-space.token").each(function(){
            sentence += $(this).text();
        });

        return sentence;
    }

    // Text to speech read the current challenge, if the original is in japanese
    function readChallenge()
    {
        var sentence = getCurrentSentence();
        if (isJapanese(sentence))
        {
            sentence = sentence.replace(/[a-zA-Zé\?\!\.\,\'\;\:]*/, "");
            readJapanese(sentence);
        }
    }

    function appendSpeakIcon()
    {
        var sentence = getCurrentSentence();
        var alreadyExists = $("#speak-japanese").length > 0;

        if (isJapanese(sentence) && !alreadyExists)
        {
            $(".text-to-translate").prepend("");
            $("#speak-japanese").off("click");
            $("#speak-japanese").on("click", readChallenge);
        }
    }

    // Should TTS play on this change?
    function shouldSpeakOnChange(mutations)
    {

        for (var mutation of mutations)
        {

            if (mutation.target.nodeType == 1) // ELEMENT_NODE
            {
                if (mutation.target.id.indexOf("submitted-text") !== -1 ||
                    mutation.target.id.indexOf("big-speaker") !== -1 ||
                    mutation.target.id.indexOf("prev-session-element-container") !== -1 )
                {
                    return false;
                }
            }
        }

        if (!isJapanese(getCurrentSentence()))
        {
            return false;
        }

        return true;
    }

    var observer = new MutationObserver(function(mutations) {
        observer.disconnect();

            // rebind hotkeys for textarea if it already exists
            $(".textarea-translate").unbind("keydown", readChallenge);
            $(".textarea-translate").bind('keydown', 'ctrl+space', readChallenge);

            furigana();

            if (shouldSpeakOnChange(mutations))
            {
                readChallenge();
            }
            appendSpeakIcon();

        observer.observe(target, config);
    });

    var config = {
        childList: true,
        characterData: true,
        subtree: true
    };

    var target;

    // the main mutation observer watches only main player div, do so only if the element actually exists
    var body = $("body")[0];
    var bodyObserver = new MutationObserver(function(mutations) {
        bodyObserver.disconnect();
            if ( $(".player-main").length )     // does player exist yet?
            {
                if (isJapaneseReverseTree())
                {
                    // MutationObserver target
                    target = $(".player-main")[0];
                    observer.observe(target, config);
                }
            } else {
                observer.disconnect();
            }
        bodyObserver.observe(body, config);
    });

    bodyObserver.observe(body, config);

})();<|endoftext|>


<commit_before>// Generated by CoffeeScript 1.6.3
(function() {
  (function(generator) {
    var Q;
    if (exports && module.exports) {
      Q = require('q');
      return module.exports = generator(Q);
    } else if (define && define.amd) {
      return define(['q'], generator);
    } else {
      return window.EZAccess = generator(Q);
    }
  })(function(Q) {
    var EZAccess;
    return EZAccess = {
      eventualObject: function(promise) {
        var shell;
        shell = {};
        promise.then(function(result) {
          var key, value, _results;
          _results = [];
          for (key in result) {
            value = result[key];
            _results.push(shell[key] = value);
          }
          return _results;
        });
        return shell;
      },
      eventualArray: function(promise) {
        var shell;
        shell = [];
        promise.then(function(result) {
          var i, value, _i, _len, _results;
          _results = [];
          for (i = _i = 0, _len = result.length; _i < _len; i = ++_i) {
            value = result[i];
            _results.push(shell.push(result[i]));
          }
          return _results;
        });
        return shell;
      },
      eventualValue: function(promise) {
        var shell;
        shell = {
          value: null
        };
        promise.then(function(result) {
          return shell.value = result;
        });
        return shell;
      },
      _constructPath: function(pattern, data) {
        var param, params, value, variable, variables;
        params = pattern.split('/');
        return ((function() {
          var _i, _len, _results;
          _results = [];
          for (_i = 0, _len = params.length; _i < _len; _i++) {
            param = params[_i];
            if (variables = param.match(/^:(.*?)(\(.*?\))?$/)) {
              variable = variables[1];
              value = data[variable];
              delete data[variable];
              _results.push(value);
            } else {
              _results.push(param);
            }
          }
          return _results;
        })()).join("/");
      },
      _constructQuery: function(data) {
        var key, result, value;
        result = (function() {
          var _ref, _results;
          _ref = data || {};
          _results = [];
          for (key in _ref) {
            value = _ref[key];
            if (value != null) {
              _results.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
            }
          }
          return _results;
        })();
        if (result.length > 0) {
          return "?" + result.join("&");
        } else {
          return "";
        }
      },
      _isFunction: function(obj) {
        return !!(obj && obj.constructor && obj.call && obj.apply);
      },
      _makeRequest: function(routeDetails, args) {
        var data,
          _this = this;
        data = this._extractData(routeDetails.argList, args);
        return (function(data) {
          var path;
          path = _this._constructPath(routeDetails.pattern, data);
          if (routeDetails.method === 'get') {
            path = path + _this._constructQuery(data);
          }
          return _this._makeRequestBase(routeDetails.method, path, data).then(function(result) {
            var error;
            if (result.success) {
              return result.response;
            } else {
              error = new Error(result.error);
              error.errors = result.errors;
              throw error;
            }
          });
        })(data);
      },
      _makeRequestBase: function(method, path, data) {
        var deferred, xmlhttp,
          _this = this;
        deferred = q.defer();
        xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
        xmlhttp.onreadystatechange = function() {
          var e;
          if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            try {
              return deferred.resolve(JSON.parse(xmlhttp.responseText));
            } catch (_error) {
              e = _error;
              console.log("EZAccessError: Response not in valid JSON", xml.responseText);
              return deferred.reject(error, "EZAccessError: Response not in valid JSON", xml.responseText);
            }
          }
        };
        if (method === 'get') {
          xmlhttp.open(method, path, true);
          xmlhttp.send();
        } else {
          xmlhttp.open(method, path, true);
          xmlhttp.setRequestHeader('Content-Type', 'application/json');
          xmlhttp.send(JSON.stringify(data));
        }
        return deferred.promise;
      }
    };
  });

  module.exports = this.EZAccess;

}).call(this);
<commit_msg>Compiled javascript<commit_after>// Generated by CoffeeScript 1.6.3
(function() {
  (function(generator) {
    var Q;
    if (exports && module.exports) {
      Q = require('q');
      return module.exports = generator(Q);
    } else if (define && define.amd) {
      return define(['q'], generator);
    } else {
      return window.EZAccess = generator(Q);
    }
  })(function(Q) {
    var EZAccess;
    return EZAccess = {
      eventualObject: function(promise) {
        var shell;
        shell = {};
        promise.then(function(result) {
          var key, value, _results;
          _results = [];
          for (key in result) {
            value = result[key];
            _results.push(shell[key] = value);
          }
          return _results;
        });
        return shell;
      },
      eventualArray: function(promise) {
        var shell;
        shell = [];
        promise.then(function(result) {
          var i, value, _i, _len, _results;
          _results = [];
          for (i = _i = 0, _len = result.length; _i < _len; i = ++_i) {
            value = result[i];
            _results.push(shell.push(result[i]));
          }
          return _results;
        });
        return shell;
      },
      eventualValue: function(promise) {
        var shell;
        shell = {
          value: null
        };
        promise.then(function(result) {
          return shell.value = result;
        });
        return shell;
      },
      _constructPath: function(pattern, data) {
        var param, params, value, variable, variables;
        params = pattern.split('/');
        return ((function() {
          var _i, _len, _results;
          _results = [];
          for (_i = 0, _len = params.length; _i < _len; _i++) {
            param = params[_i];
            if (variables = param.match(/^:(.*?)(\(.*?\))?$/)) {
              variable = variables[1];
              value = data[variable];
              delete data[variable];
              _results.push(value);
            } else {
              _results.push(param);
            }
          }
          return _results;
        })()).join("/");
      },
      _constructQuery: function(data) {
        var key, result, value;
        result = (function() {
          var _ref, _results;
          _ref = data || {};
          _results = [];
          for (key in _ref) {
            value = _ref[key];
            if (value != null) {
              _results.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
            }
          }
          return _results;
        })();
        if (result.length > 0) {
          return "?" + result.join("&");
        } else {
          return "";
        }
      },
      _isFunction: function(obj) {
        return !!(obj && obj.constructor && obj.call && obj.apply);
      },
      _makeRequest: function(routeDetails, args) {
        var data,
          _this = this;
        data = this._extractData(routeDetails.argList, args);
        return (function(data) {
          var path;
          path = _this._constructPath(routeDetails.pattern, data);
          if (routeDetails.method === 'get') {
            path = path + _this._constructQuery(data);
          }
          return _this._makeRequestBase(routeDetails.method, path, data).then(function(result) {
            var error;
            if (result.success) {
              return result.response;
            } else {
              error = new Error(result.error);
              error.errors = result.errors;
              throw error;
            }
          });
        })(data);
      },
      _makeRequestBase: function(method, path, data) {
        var deferred, xmlhttp,
          _this = this;
        deferred = q.defer();
        xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
        xmlhttp.onreadystatechange = function() {
          var e;
          if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
            try {
              return deferred.resolve(JSON.parse(xmlhttp.responseText));
            } catch (_error) {
              e = _error;
              console.log("EZAccessError: Response not in valid JSON", xml.responseText);
              return deferred.reject(error, "EZAccessError: Response not in valid JSON", xml.responseText);
            }
          }
        };
        if (method === 'get') {
          xmlhttp.open(method, path, true);
          xmlhttp.send();
        } else {
          xmlhttp.open(method, path, true);
          xmlhttp.setRequestHeader('Content-Type', 'application/json');
          xmlhttp.send(JSON.stringify(data));
        }
        return deferred.promise;
      }
    };
  });

}).call(this);
<|endoftext|>


<commit_before>define(["filters/nodefilter"], function (nodefilter) {
  return function (name, key, value, f) {
    var negate = false
    var refresh

    function run(d) {
      var o = dictGet(d, key.slice(0))

      if (f)
        o = f(o)

      return o === value ? !negate : negate
    }

    function setRefresh(f) {
      refresh = f
    }

    function draw(el) {
      if (negate)
        el.parentNode.classList.add("not")
      else
        el.parentNode.classList.remove("not")
    }

    function render(el) {
      var label = document.createElement("label")
      label.textContent = name + " "

      var strong = document.createElement("strong")
      strong.textContent = value

      draw(el)

      label.appendChild(strong)
      label.onclick = function () {
        negate = !negate

        draw(el)

        if (refresh)
          refresh()
      }

      el.appendChild(label)
    }

    return { run: nodefilter(run),
             setRefresh: setRefresh,
             render: render
           }
  }
})
<commit_msg>genericnode: show negation in label<commit_after>define(["filters/nodefilter"], function (nodefilter) {
  return function (name, key, value, f) {
    var negate = false
    var refresh

    var label = document.createElement("label")
    var strong = document.createElement("strong")
    label.textContent = name + " "
    label.appendChild(strong)

    function run(d) {
      var o = dictGet(d, key.slice(0))

      if (f)
        o = f(o)

      return o === value ? !negate : negate
    }

    function setRefresh(f) {
      refresh = f
    }

    function draw(el) {
      if (negate)
        el.parentNode.classList.add("not")
      else
        el.parentNode.classList.remove("not")

      strong.textContent = (negate ? "¬" : "" ) + value
    }

    function render(el) {
      el.appendChild(label)
      draw(el)

      label.onclick = function () {
        negate = !negate

        draw(el)

        if (refresh)
          refresh()
      }
    }

    return { run: nodefilter(run),
             setRefresh: setRefresh,
             render: render
           }
  }
})
<|endoftext|>