/
Jonecuper
/
fb-reader-for-mozilla
Обзор
Документация
Войти
/
Jonecuper
/
fb-reader-for-mozilla
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/streamfilter.js
197 строк
7 KB
Jonecuper
Initial commit: FB2 Night Reader v2.0
29 май 2026, 23:10
29 май 2026, 23:10
3e0c9d2
Код
Авторство
О чём код?
'use strict'; (function() { var STORAGE_KEY = 'fb2settings'; var HTML_NS = 'http://www.w3.org/1999/xhtml'; var cssBaseUrl = browser.runtime.getURL(''); function buildHTMLPage(userSettings) { var effective = userSettings ? FB2Settings.mergeWithDefaults(userSettings) : FB2Settings.DEFAULTS; var cssVars = FB2Settings.buildCSS(effective); return ( '<html xmlns="http://www.w3.org/1999/xhtml" ' + 'xmlns:fb="http://www.gribuser.ru/xml/fictionbook/2.0">' + '<head>' + '<title>Untitled FB2</title>' + '<link rel="icon" sizes="16x16" href="' + cssBaseUrl + 'icons/icon-16.png" type="image/png" />' + '<link rel="icon" sizes="32x32" href="' + cssBaseUrl + 'icons/icon-16-2x.png" type="image/png" />' + '<link rel="stylesheet" href="' + cssBaseUrl + 'css/fb2.css" />' + '<link rel="stylesheet" href="' + cssBaseUrl + 'css/html.css" />' + '<link rel="stylesheet" href="' + cssBaseUrl + 'css/print.css" media="print" />' + '<style id="fb2-settings" type="text/css">' + cssVars + '</style>' + '<script src="' + cssBaseUrl + 'tools.js"></' + 'script>' + '<script>document.addEventListener("DOMContentLoaded",function(){FB2Tools.init(document);});</' + 'script>' + '</head>' + '<body>' + '<div id="contents"><div>\u00A7</div></div>' + '</body>' + '</html>' ); } function extractFromZip(data) { return JSZip.loadAsync(data).then(function(zip) { var files = zip.files || zip; for (var name in files) { if (files.hasOwnProperty(name) && name.toLowerCase().endsWith('.fb2')) { return files[name].async('uint8array'); } } return data; }).catch(function() { return data; }); } function detectCharset(data) { var decoder = new TextDecoder(); var header = decoder.decode(data.slice(0, 100)); var match = header.match(/<?xml\s+version\s*=\s*["']1\.0["']\s+encoding\s*=\s*["'](.*?)["']/i); return match ? match[1] : 'utf-8'; } function loadSettings() { return browser.storage.local.get(STORAGE_KEY).then(function(result) { return result[STORAGE_KEY] || null; }).catch(function() { return null; }); } function processBook(rawData, userSettings, filter) { var charset = detectCharset(rawData); var decoder = new TextDecoder(charset); var xmlText = decoder.decode(rawData); var parser = new DOMParser(); var serializer = new XMLSerializer(); var encoder = new TextEncoder(); var bookTree = parser.parseFromString(xmlText, 'application/xml'); var htmlText = buildHTMLPage(userSettings); var bookHTML = parser.parseFromString(htmlText, 'application/xml'); var titleTags = bookTree.getElementsByTagName('book-title'); if (titleTags.length > 0) { var titleEl = bookHTML.getElementsByTagName('title')[0]; if (titleEl) titleEl.textContent = titleTags[0].textContent; } var fbRoot = bookHTML.adoptNode(bookTree.documentElement); bookHTML.getElementsByTagName('body')[0].appendChild(fbRoot); var merged = serializer.serializeToString(bookHTML); filter.write(encoder.encode(merged)); filter.disconnect(); } var FB2_URL_RE = /\.fb2(\.zip)?([?#].*)?$/i; browser.webRequest.onHeadersReceived.addListener( function(details) { if (details.statusCode !== 200 || !details.url.match(FB2_URL_RE)) { return {}; } removeResponseHeader(details.responseHeaders, 'Content-Disposition'); setResponseHeader(details.responseHeaders, 'Content-Type', 'text/xml; charset=utf-8'); var filter = browser.webRequest.filterResponseData(details.requestId); var receivedData = new Uint8Array(); filter.ondata = function(event) { var chunk = new Uint8Array(event.data); receivedData = mergeBuffers(receivedData, chunk); }; filter.onstop = function(event) { var data = receivedData; var processPromise; if (data.length >= 2 && data[0] === 80 && data[1] === 75) { processPromise = extractFromZip(data); } else { processPromise = Promise.resolve(data); } processPromise.then(function(bookData) { return loadSettings().then(function(userSettings) { processBook(bookData, userSettings, filter); }); }).catch(function(err) { console.error('FB2 stream filter error:', err); try { filter.write(receivedData); } catch (e) {} try { filter.disconnect(); } catch (e) {} }); }; return { responseHeaders: details.responseHeaders }; }, { urls: [ '*://*/*.fb2*', '*://*/*.FB2*', '*://*/*.Fb2*', '*://*/*.fB2*' ], types: ['main_frame'] }, ['blocking', 'responseHeaders'] ); function setResponseHeader(headers, name, value) { for (var i = 0; i < headers.length; i++) { if (headers[i].name.toLowerCase() === name.toLowerCase()) { headers[i].value = value; return; } } headers.push({ name: name, value: value }); } function removeResponseHeader(headers, name) { for (var i = headers.length - 1; i >= 0; i--) { if (headers[i].name.toLowerCase() === name.toLowerCase()) { headers.splice(i, 1); } } } function mergeBuffers(a, b) { var c = new Uint8Array(a.length + b.length); c.set(a); c.set(b, a.length); return c; } browser.runtime.onMessage.addListener(function(msg, sender, sendResponse) { if (msg.action !== 'applySettings') return; var settings = msg.settings; var obj = {}; obj[STORAGE_KEY] = settings; browser.storage.local.set(obj).then(function() { return browser.tabs.query({ active: true, currentWindow: true }); }).then(function(tabs) { if (!tabs || tabs.length === 0) return; var cssVars = FB2Settings.buildCSS(settings); var code = ( '(function(){var el=document.getElementById("fb2-settings");' + 'if(!el){el=document.createElementNS("http://www.w3.org/1999/xhtml","style");' + 'el.id="fb2-settings";el.setAttribute("type","text/css");' + 'var h=document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml","head")[0];' + '(h||document.head||document.documentElement).appendChild(el);}' + 'el.textContent=' + JSON.stringify(cssVars) + ';})()' ); return browser.tabs.executeScript(tabs[0].id, { code: code }); }).catch(function(err) { console.error('FB2 bg apply error:', err); }); }); })();