Initial round of re-branding, I temporarily am using the dta icon just rotated 180, waiting for a permanent icon to use Removed the initial create tab for dta but left any download links as is Added webextension-polyfill-ts to enable retrieval of cookies Removed all access and preferences to the download manager as it is not relevant in this fork
262 lines
6.6 KiB
TypeScript
262 lines
6.6 KiB
TypeScript
"use strict";
|
|
// License: MIT
|
|
|
|
import { getTextLinks } from "./textlinks";
|
|
// eslint-disable-next-line no-unused-vars
|
|
import { BaseItem } from "./item";
|
|
import { ALLOWED_SCHEMES } from "./constants";
|
|
|
|
export const NS_METALINK_RFC5854 = "urn:ietf:params:xml:ns:metalink";
|
|
export const NS_DTA = "http://www.downthemall.net/properties#";
|
|
|
|
function parseNum(
|
|
file: Element,
|
|
attr: string,
|
|
defaultValue: number,
|
|
ns = NS_METALINK_RFC5854) {
|
|
const val = file.getAttributeNS(ns, attr);
|
|
if (!val) {
|
|
return defaultValue + 1;
|
|
}
|
|
const num = parseInt(val, 10);
|
|
if (isFinite(num)) {
|
|
return num;
|
|
}
|
|
return defaultValue + 1;
|
|
}
|
|
|
|
function importMeta4(data: string) {
|
|
const parser = new DOMParser();
|
|
const document = parser.parseFromString(data, "text/xml");
|
|
const {documentElement} = document;
|
|
const items: BaseItem[] = [];
|
|
let batch = 0;
|
|
for (const file of documentElement.querySelectorAll("file")) {
|
|
try {
|
|
const url = Array.from(file.querySelectorAll("url")).map(u => {
|
|
try {
|
|
const {textContent} = u;
|
|
if (!textContent) {
|
|
return null;
|
|
}
|
|
const url = new URL(textContent);
|
|
if (!ALLOWED_SCHEMES.has(url.protocol)) {
|
|
return null;
|
|
}
|
|
const prio = parseNum(u, "priority", 0);
|
|
return {
|
|
url,
|
|
prio
|
|
};
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}).filter(u => !!u).reduce((p, c) => {
|
|
if (!c) {
|
|
return null;
|
|
}
|
|
if (!p || p.prio < c.prio) {
|
|
return c;
|
|
}
|
|
return p;
|
|
});
|
|
if (!url) {
|
|
continue;
|
|
}
|
|
batch = parseNum(file, "num", batch, NS_DTA);
|
|
const idx = parseNum(file, "idx", 0, NS_DTA);
|
|
const item: BaseItem = {
|
|
url: url.url.toString(),
|
|
usable: decodeURIComponent(url.url.toString()),
|
|
batch,
|
|
idx
|
|
};
|
|
const ref = file.getAttributeNS(NS_DTA, "referrer");
|
|
if (ref) {
|
|
item.referrer = ref;
|
|
item.usableReferrer = decodeURIComponent(ref);
|
|
}
|
|
const mask = file.getAttributeNS(NS_DTA, "mask");
|
|
if (mask) {
|
|
item.mask = mask;
|
|
}
|
|
const description = file.querySelector("description");
|
|
if (description && description.textContent) {
|
|
item.description = description.textContent.trim();
|
|
}
|
|
const title = file.getElementsByTagNameNS(NS_DTA, "title");
|
|
if (title && title[0] && title[0].textContent) {
|
|
item.title = title[0].textContent;
|
|
}
|
|
items.push(item);
|
|
}
|
|
catch (ex) {
|
|
console.error("Failed to import file", ex);
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
function parseKV(current: BaseItem, line: string) {
|
|
const [k, v] = line.split("=", 2);
|
|
switch (k.toLocaleLowerCase().trim()) {
|
|
case "referer": {
|
|
const refererUrls = getTextLinks(v);
|
|
if (refererUrls && refererUrls.length) {
|
|
current.referrer = refererUrls.pop();
|
|
current.usableReferrer = decodeURIComponent(current.referrer || "");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
export function importText(data: string) {
|
|
if (data.includes(NS_METALINK_RFC5854)) {
|
|
return importMeta4(data);
|
|
}
|
|
const splitter = /((?:.|\r)+)\n|(.+)$/g;
|
|
const spacer = /^\s+/;
|
|
let match;
|
|
let current: BaseItem | undefined = undefined;
|
|
let idx = 0;
|
|
const items = [];
|
|
while ((match = splitter.exec(data)) !== null) {
|
|
try {
|
|
const line = match[0].trimRight();
|
|
if (!line) {
|
|
continue;
|
|
}
|
|
if (spacer.test(line)) {
|
|
if (!current) {
|
|
continue;
|
|
}
|
|
parseKV(current, line);
|
|
continue;
|
|
}
|
|
const urls = getTextLinks(line);
|
|
if (!urls || !urls.length) {
|
|
continue;
|
|
}
|
|
current = {
|
|
url: urls[0],
|
|
usable: decodeURIComponent(urls[0]),
|
|
idx: ++idx
|
|
};
|
|
items.push(current);
|
|
}
|
|
catch (ex) {
|
|
current = undefined;
|
|
console.error("Failed to import", ex);
|
|
}
|
|
}
|
|
return items;
|
|
}
|
|
|
|
export interface Exporter {
|
|
fileName: string;
|
|
getText(items: BaseItem[]): string;
|
|
}
|
|
|
|
class TextExporter {
|
|
readonly fileName: string;
|
|
|
|
constructor() {
|
|
this.fileName = "links.txt";
|
|
}
|
|
|
|
getText(items: BaseItem[]) {
|
|
const lines = [];
|
|
for (const item of items) {
|
|
lines.push(item.url);
|
|
}
|
|
return lines.join("\n");
|
|
}
|
|
}
|
|
|
|
class Aria2Exporter {
|
|
readonly fileName: string;
|
|
|
|
constructor() {
|
|
this.fileName = "links.aria2.txt";
|
|
}
|
|
|
|
getText(items: BaseItem[]) {
|
|
const lines = [];
|
|
for (const item of items) {
|
|
lines.push(item.url);
|
|
if (item.referrer) {
|
|
lines.push(` referer=${item.referrer}`);
|
|
}
|
|
}
|
|
return lines.join("\n");
|
|
}
|
|
}
|
|
|
|
class MetalinkExporter {
|
|
readonly fileName: string;
|
|
|
|
constructor() {
|
|
this.fileName = "links.meta4";
|
|
}
|
|
|
|
getText(items: BaseItem[]) {
|
|
const document = window.document.implementation.
|
|
createDocument(NS_METALINK_RFC5854, "metalink", null);
|
|
const root = document.documentElement;
|
|
root.setAttributeNS(NS_DTA, "generator", "TraitorousDownloading!");
|
|
root.appendChild(document.createComment(
|
|
"metalink as exported by TraitorousDownloading!",
|
|
));
|
|
|
|
for (const item of items) {
|
|
const anyItem = item as any;
|
|
const f = document.createElementNS(NS_METALINK_RFC5854, "file");
|
|
f.setAttribute("name", anyItem.currentName);
|
|
if (item.batch) {
|
|
f.setAttributeNS(NS_DTA, "num", item.batch.toString());
|
|
}
|
|
if (item.idx) {
|
|
f.setAttributeNS(NS_DTA, "idx", item.idx.toString());
|
|
}
|
|
if (item.referrer) {
|
|
f.setAttributeNS(NS_DTA, "referrer", item.referrer);
|
|
}
|
|
if (item.mask) {
|
|
f.setAttributeNS(NS_DTA, "mask", item.mask);
|
|
}
|
|
|
|
if (item.description) {
|
|
const n = document.createElementNS(NS_METALINK_RFC5854, "description");
|
|
n.textContent = item.description;
|
|
f.appendChild(n);
|
|
}
|
|
|
|
if (item.title) {
|
|
const n = document.createElementNS(NS_DTA, "title");
|
|
n.textContent = item.title;
|
|
f.appendChild(n);
|
|
}
|
|
|
|
const u = document.createElementNS(NS_METALINK_RFC5854, "url");
|
|
u.textContent = item.url;
|
|
f.appendChild(u);
|
|
|
|
if (anyItem.totalSize > 0) {
|
|
const s = document.createElementNS(NS_METALINK_RFC5854, "size");
|
|
s.textContent = anyItem.totalSize.toString();
|
|
f.appendChild(s);
|
|
}
|
|
root.appendChild(f);
|
|
}
|
|
let xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
|
xml += root.outerHTML;
|
|
return xml;
|
|
}
|
|
}
|
|
|
|
export const textExporter = new TextExporter();
|
|
export const aria2Exporter = new Aria2Exporter();
|
|
export const metalinkExporter = new MetalinkExporter();
|