Для удобства понимания и поддержки сообществом материал разделен на три части: анализ общего файла утилит Common.js, анализ логики взаимодействия Service.js и детальный разбор работы сетевого моста QuillBotBridge.ps1.
Архитектура расширений QTranslate
 
Так как ядро QTranslate закрыто и работает на базе встроенного движка Active Scripting (JScript/IE), в нем отсутствуют современные API вроде Fetch или WebSockets.
 
Для обхода этого ограничения разработчики использовали следующую схему:
 
    Common.js: Предоставляет стандартные структуры данных, полифиллы массивов, работу с кодировками и строками.
 
    Service.js: Выступает фасадом для QTranslate. Он собирает параметры перевода, кодирует текст в Base64, записывает параметры в переменные окружения ОС и запускает внешний PowerShell-скрипт через ActiveX (WScript.Shell).
 
    QuillBotBridge.ps1: PowerShell-скрипт запускается в скрытом режиме, считывает переменные окружения, открывает современное WebSocket-соединение с сервером QuillBot, обменивается пакетами по их собственному протоколу, сохраняет результат работы в JSON-файл во временную папку и завершает работу. JScript считывает этот файл и отдает результат обратно в программу.

Часть 1. Разбор общего файла Common.js

Этот скрипт загружается глобально для всех переводчиков. Он содержит базовую инфраструктуру, вспомогательные функции манипуляции с HTML, кодирования параметров, JSON-сериализации и полифиллы для старых версий JScript (эквивалент ES3/ES5 в Internet Explorer).
 
Вот этот код с подробными построчными комментариями и описанием функций:
code JavaScript

// ============================================================================
// ГЛОБАЛЬНЫЕ КОНСТАНТЫ И ОПРЕДЕЛЕНИЯ
// ============================================================================
 
// Базовые ограничения и символы переноса строк
var Const = {
    MAX_URI_LEN: 1800,       // Максимальная длина URI для GET-запросов
    MAX_SOURCE_LEN: 5E3,     // Максимальная длина исходного текста (5000 символов)
    NL: "\r\n",              // Стандартный перевод строки Windows
    NL2: "\r\n\r\n"          // Двойной перевод строки
};
 
// Битовая маска возможностей сервиса (для QTranslate)
var Capability = {
    TRANSLATE: 1,            // Сервис умеет переводить текст
    DETECT_LANGUAGE: 2,      // Сервис умеет определять язык
    LISTEN: 4,               // Сервис поддерживает озвучку (TTS)
    DICTIONARY: 8            // Сервис поддерживает словарные статьи
};
 
// Перечисление используемых HTTP-методов
var HttpMethod = {
    UNDEFINED: 0,
    GET: 1,
    POST: 2
};
 
// Поддерживаемые кодовые страницы (используются при отправке запросов)
var CodePage = {
    WINDOWS1251: 1251,
    UTF8: 65001,
    ISO8859_1: 28591
};
 
var toString = Object.prototype.toString,
    isArray = Array.isArray || function(a) {  
        return "[object Array]" === toString.call(a);  
    };
 
// ============================================================================
// КОНСТРУКТОРЫ КЛАССОВ И СТРУКТУР QTRANSLATE
// ============================================================================
 
/**
 * Описывает метаданные сервиса перевода для интерфейса QTranslate.
 * @param {number} a - Уникальный ID сервиса.
 * @param {string} b - Отображаемое название сервиса.
 * @param {string} c - Описание/копирайт сервиса.
 * @param {number} d - Битовая маска возможностей (Capability).
 */
function ServiceHeader(a, b, c, d) {
    this.id = a;
    this.name = b || "";
    this.info = c || "";
    this.capabilities = d;
}
 
/**
 * Описывает структуру HTTP-запроса, который QTranslate выполнит сам.
 * @param {number} a - Метод (HttpMethod).
 * @param {string} b - URI запроса.
 * @param {string} c - Данные для POST/PUT тела запроса.
 * @param {string} d - HTTP заголовки.
 * @param {number} e - Кодовая страница (CodePage).
 * @param {string} g - Имя callback-функции JScript, которая обработает ответ.
 */
function RequestData(a, b, c, d, e, g) {
    this.method = a;
    this.uri = b || "";
    this.data = c || "";
    this.headers = d || (a === HttpMethod.UNDEFINED ? "" : a === HttpMethod.GET ? getHeader() : postHeader());
    this.codepage = e || CodePage.UTF8;
    this.responseHandler = g || "";
}
 
/**
 * Структура ответа, возвращаемая обработчиком QTranslate.
 * @param {string} a - Текст перевода.
 * @param {number} b - Определенный исходный язык (ID).
 * @param {number} c - Язык перевода (ID).
 * @param {string} d - Дополнительные данные (для отладки/словари).
 * @param {string} e - Имя следующего обработчика запроса (для многоэтапных запросов).
 */
function ResponseData(a, b, c, d, e) {
    this.translation = a || "";
    this.sourceLanguage = b;
    this.translationLanguage = c;
    this.data = d || "";
    this.nextRequestHandler = e || "";
}
 
// Глобальный объект настроек, заполняемый самой программой QTranslate
var Options = {};  
 
/** Добавление опции в конфигурацию */
function addOption(a, b) {
    Options[a] = b;
}
 
// ============================================================================
// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ФОРМИРОВАНИЯ HTTP
// ============================================================================
 
/** Генерация заголовков по умолчанию для GET-запроса */
function getHeader() {
    var a = format("Accept-Language: {0};q=0.8,en-US;q=0.6,en;q=0.4", Options.LanguageCode);
    return "Accept: */*" + Const.NL + a + Const.NL + "Accept-Encoding: gzip,deflate" + Const.NL + "Accept-Charset: utf-8";
}
 
/**
 * Генерация заголовков по умолчанию для POST-запроса.
 * @param {boolean} a - Флаг формата JSON (true = application/json, false = urlencoded).
 */
function postHeader(a) {
    a = a ? "application/json" : "application/x-www-form-urlencoded";
    return getHeader() + Const.NL + "Content-Type: " + a + "; charset=utf-8";
}
 
/** Кодирование параметра для URL (стандартный URI Encode) */
function encodeUriParam(a) {
    return a ? encodeURIComponent(a) : "";
}
 
/**  
 * Кодирование параметра для GET-запроса с учетом ограничений длины URL.
 * Обрезает строку по пробелу (%20), чтобы ссылка осталась валидной.
 */
function encodeGetParam(a) {
    a = encodeUriParam(a);
    if (a.length > Const.MAX_URI_LEN) {
        a = a.slice(0, Const.MAX_URI_LEN);
        var b = a.lastIndexOf("%20");
        if (0 < b) return a.slice(0, b);
    }
    return a;
}
 
/** Кодирование параметра для POST тела */
function encodePostParam(a) {
    return encodeUriParam(a);
}
 
/** Нормализация переносов строк (превращает Windows CRLF и Mac CR в Linux LF) */
function prepareSource(a) {
    return (a || "").replace(/(\r\n|\r)/g, "\n");
}
 
/** Ограничение текста по лимиту символов */
function limitSource(a, b) {
    b = b || Const.MAX_SOURCE_LEN;
    return a && a.length > b ? a.slice(0, b) : a;
}
 
// ============================================================================
// РАБОТА СО СТРОКАМИ И HTML
// ============================================================================
 
/** Форматирование строки а-ля C# String.Format ("Hello {0}", name) */
function format() {
    for (var a = arguments[0], b = 0; b < arguments.length - 1; b++) {
        a = a.replace(new RegExp("\\{" + b + "\\}", "gm"), arguments[b + 1]);
    }
    return a;
}
 
/** Поиск подстроки или регулярного выражения в строке, возвращает объект {index, length} */
function stringFind(a, b) {
    if (b) {
        if ("string" === typeof b) {
            var c = a.indexOf(b);
            if (-1 < c) return { index: c, length: b.length };
        } else if (c = a.match(b)) {
            return { index: c.index, length: c[0].length };
        }
    }
}
 
/** Извлечение подстроки между двумя маркерами */
function stringFindSub(a, b, c, d, e) {
    return a && b && d && (b = stringFind(a, b)) &&  
           (a = a.slice(c ? b.index : b.index + b.length), b = stringFind(a, d)) ?  
           a.slice(0, e ? b.index + b.length : b.index) : "";
}
 
/** Обрезка пробельных символов в начале и конце строки */
function trimString(a) {
    return a ? a.replace(/(^\s+)|(\s+$)/g, "") : "";
}
 
function startsWith(a, b) {
    return a.slice(0, b.length) === b;
}
 
function endsWith(a, b) {
    return -1 !== a.indexOf(b, a.length - b.length);
}
 
function removeIfStartsWith(a, b) {
    return startsWith(a, b) ? a.slice(b.length) : a;
}
 
// Проверка на баг разделения строк регулярными выражениями в старых IE
var IE_SPLIT_ISSUE = 3 !== "a'b".split(/(')/g).length;
 
/** Безопасный split с регулярными выражениями для совместимости со старыми IE */
function stringSplit(a, b) {
    if (!IE_SPLIT_ISSUE) return a.split(b);
    var c = [];
    b.global = !0;
    for (var d = b.exec(a); d;) {
        c.push(a.substring(0, d.index));
        for (var e = 1, g = d.length; e < g; e++) c.push(d[e]);
        a = a.substring(b.lastIndex, a.length);
        d = b.exec(a);
    }
    c.push(a);
    return c;
}
 
/** Декодирование основных сущностей HTML (& -> &, < -> < и т.д.) */
function unquoteHtml(a) {
    if (!a) return "";
    a = a.replace(/&[;]?/g, "&");
    a = a.replace(/<[;]?/g, "<");
    a = a.replace(/>[;]?/g, ">");
    a = a.replace(/"[;]?/g, '"');
    return a.replace(/&#[0]?39[;]/g, "'");
}
 
/** Удаление лишних пустых строк и приведение структуры текста к CRLF */
function removeEmptyLines(a) {
    if (!a) return "";
    a = a.replace(/\r/gi, "");
    a = a.split("\n");
    for (var b = 0; b < a.length; b++) {
        a[b].match(/[\S]/g) || (a[b] = "");
    }
    a = a.join("\n").replace(/\n{3,}/g, "\n\n");
    a = a.replace(/\n/gi, Const.NL);
    return trimString(a);
}
 
/** Очистка текста от HTML тегов, стилей, скриптов с сохранением форматирования переносов */
function stripHtml(a) {
    if (!a) return "";
    a = a.replace(/\x3c!--[\s\S]*?--\x3e/g, ""); // Удаление комментариев <!-- ... -->
    a = a.replace(/<(style|script)(.|\s)*?(style|script)>/g, ""); // Удаление CSS и JS тегов
    a = a.replace(/<br\/>/gi, Const.NL); // Преобразование BR в переносы
    a = a.replace(/<[^>]+>/g, ""); // Удаление всех тегов <...>
    a = a.replace(/ /g, " ");
    a = a.replace(/'/gi, "'");
    a = a.replace(/ +(?= )/g, ""); // Сжатие множественных пробелов
    return removeEmptyLines(a);
}
 
/** Служебная функция удаления по регулярному выражению на основе массива ключевых слов */
function regExpRemove(a, b, c) {
    if (!b) return a;
    b = format(c, b.join("|"));
    return a.replace(new RegExp(b, "g"), "");
}
 
/** Удаление конкретных HTML атрибутов из строки */
function removeAttributes(a, b) {
    return regExpRemove(a, b, " ({0})(?:\\s*=\\s*(??:\"((?:\\.|[^\"])*)\")|(?:'((?:\\.|[^'])*)')|([^>\\s]+)))?");
}
 
/** Удаление тегов по списку имён (например, ['span', 'div']) */
function removeTags(a, b) {
    return regExpRemove(a, b, "</?({0})[^>]*>");
}
 
/** Удаление элементов целиком вместе с содержимым */
function removeElements(a, b) {
    return regExpRemove(a, b, "<\\s*({0})[^>]*>((.|\n)*?)<\\s*/\\s*({0})>");
}
 
/** Преобразование относительных путей в HTML ссылках (href/src) в абсолютные */
function updateHtmlLinks(a, b) {
    if (!a) return "";
    var c = startsWith(b, "https") ? "https://" : "http://";
    b = removeIfStartsWith(b, c);
    endsWith(b, "/") || (b += "/");
    a = a.replace(/href="\/\//gi, 'href="' + c);
    a = a.replace(/href="\//gi, 'href="' + c + b);
    a = a.replace(/src="\/\//gi, 'src="' + c);
    a = a.replace(/src="\//gi, 'src="' + c + b);
    a = a.replace(/href=' \/\//gi, "href='" + c);
    a = a.replace(/href='\/ /gi, "href='" + c + b);
    a = a.replace(/src=' \/\//gi, "src='" + c);
    return a = a.replace(/src='\/ /gi, "src='" + c + b);
}
 
// ============================================================================
// РАБОТА С ЯЗЫКАМИ И КОДАМИ
// ============================================================================
 
var SupportedLanguages = null,
    UNKNOWN_LANGUAGE_CODE = -1,
    UNKNOWN_LANGUAGE = 0,
    AUTO_DETECT_LANGUAGE = 1,
    ENGLISH_LANGUAGE = 17;
 
/** Проверка, валидный ли ID языка в массиве поддержки */
function isLanguage(a) {
    return a > AUTO_DETECT_LANGUAGE && a < SupportedLanguages.length;
}
 
/** Получение строкового кода (например, "ru") по ID языка */
function codeFromLanguage(a) {
    return a === AUTO_DETECT_LANGUAGE || isLanguage(a) ? SupportedLanguages[a] : UNKNOWN_LANGUAGE_CODE;
}
 
/** Получение ID языка по его строковому коду */
function languageFromCode(a) {
    if (SupportedLanguages[ENGLISH_LANGUAGE] === a) return ENGLISH_LANGUAGE;
    for (var b = AUTO_DETECT_LANGUAGE; b < SupportedLanguages.length; b++) {
        if (SupportedLanguages[b] === a) return b;
    }
    return UNKNOWN_LANGUAGE;
}
 
/** Поддерживает ли сервис автоопределение языка */
function usesAutoDetectCode() {
    return SupportedLanguages[AUTO_DETECT_LANGUAGE] !== UNKNOWN_LANGUAGE_CODE;
}
 
// ============================================================================
// JSON ПАРСЕРЫ (СОВМЕСТИМОСТЬ)
// ============================================================================
 
/** Безопасный парсинг JSON без нативного JSON.parse (использует конструктор Function) */
function parseJSON(a) {
    return (new Function("return " + a))();
}
 
/** Сериализатор JSON объектов в строку для старых версий JScript */
var stringifyJSON = function() {
    var a = {
            '"': '\\"', "\\": "\\\\", "\b": "\\b", "\f": "\\f",
            "\n": "\\n", "\r": "\\r", "\t": "\\t"
        },
        b = function(b) {
            return a[b] || "\\u" + (b.charCodeAt(0) + 65536).toString(16).substr(1);
        },
        c = /[\\"\u0000-\u001F\u2028\u2029]/g;
    return function e(a) {
        if (null === a) return "null";
        if ("number" === typeof a) return isFinite(a) ? a.toString() : "null";
        if ("boolean" === typeof a) return a.toString();
        if ("object" === typeof a) {
            if ("function" === typeof a.toJSON) return e(a.toJSON());
            if (isArray(a)) {
                for (var h = "[", f = 0; f < a.length; f++) {
                    h += (f ? ", " : "") + e(a[f]);
                }
                return h + "]";
            }
            if ("[object Object]" === toString.call(a)) {
                h = [];
                for (f in a) {
                    a.hasOwnProperty(f) && h.push(e(f) + ": " + e(a[f]));
                }
                return "{" + h.join(", ") + "}";
            }
        }
        return '"' + a.toString().replace(c, b) + '"';
    };
}();
 
// ============================================================================
// ПОЛИФИЛЛЫ ДЛЯ СТАНДАРТНЫХ МЕТОДОВ МАССИВОВ (ES5)
// ============================================================================
Array.prototype.map || (Array.prototype.map = function(a) {
    for (var b = Object(this), c = b.length >>> 0, d = Array(c), e = 0; e < c;) {
        e in b && (d[e] = a(b[e], e, b)), e++;
    }
    return d;
});
 
Array.prototype.forEach || (Array.prototype.forEach = function(a) {
    for (var b = Object(this), c = b.length >>> 0, d = 0; d < c;) {
        d in b && a(b[d], d, b), d++;
    }
});
 
Array.prototype.filter || (Array.prototype.filter = function(a) {
    for (var b = Object(this), c = b.length >>> 0, d = 0, e = [], g; d < c;) {
        d in b && (g = b[d], a(g, d, b) && e.push(g)), d++;
    }
    return e;
});