Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
e4b0629dee | |||
5c2700ca36 | |||
639a582804 | |||
2d1f185fcd | |||
38735ed0ae | |||
216bc590da | |||
1c10d8005a | |||
1fcfbe5360 | |||
8d3dda1cec | |||
be18f667d9 | |||
027b2c4fb1 | |||
4ed92878be | |||
a6930f309e | |||
fdcdae0412 | |||
2c18ddaaa8 | |||
994e7ad0a6 | |||
95536b36be | |||
9c159d5d24 | |||
42ccfd5dc5 | |||
dabf7f8a28 | |||
9cac48f439 | |||
ef6bc840d8 | |||
1c38ec1357 | |||
a4436bd6c8 | |||
5a4b8143b2 | |||
00a5712427 |
@ -77,3 +77,7 @@ Licensed under the Mozilla Public License 2.0.
|
|||||||
|
|
||||||
The list itself is licensed under the Mozilla Public License 2.0.
|
The list itself is licensed under the Mozilla Public License 2.0.
|
||||||
The javascript library accessing it is licensed under the MIT license.
|
The javascript library accessing it is licensed under the MIT license.
|
||||||
|
|
||||||
|
## whatwg-mimetype
|
||||||
|
|
||||||
|
Licensed under MIT
|
||||||
|
@ -13,5 +13,6 @@
|
|||||||
"pl": "Polski (PL) [pl]",
|
"pl": "Polski (PL) [pl]",
|
||||||
"pt": "Português (Brasil) [pt]",
|
"pt": "Português (Brasil) [pt]",
|
||||||
"ru": "Русский [ru]",
|
"ru": "Русский [ru]",
|
||||||
"zh_CN": "简体中文 [zh_CN]"
|
"zh_CN": "简体中文 [zh_CN]",
|
||||||
|
"zh_TW": "正體中文 [zh_TW]"
|
||||||
}
|
}
|
@ -684,7 +684,7 @@
|
|||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
"pref_hide_context": {
|
"pref_hide_context": {
|
||||||
"message": "Nerodyti bendrųjų kontekstinio meniu elementų",
|
"message": "Kontekstiniame meniu nerodyti bendrųjų elementų",
|
||||||
"description": "Preferences/General"
|
"description": "Preferences/General"
|
||||||
},
|
},
|
||||||
"pref_manager_tooltip": {
|
"pref_manager_tooltip": {
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"description": "Action: Add paused"
|
"description": "Action: Add paused"
|
||||||
},
|
},
|
||||||
"add_download": {
|
"add_download": {
|
||||||
"message": "добавить закачку",
|
"message": "Добавить закачку",
|
||||||
"description": "Action for adding a download"
|
"description": "Action for adding a download"
|
||||||
},
|
},
|
||||||
"add_new": {
|
"add_new": {
|
||||||
@ -244,7 +244,7 @@
|
|||||||
"description": "OneClick! action; Menu text"
|
"description": "OneClick! action; Menu text"
|
||||||
},
|
},
|
||||||
"dta_turbo_all": {
|
"dta_turbo_all": {
|
||||||
"message": "ОднимКликом! - Все вкладки",
|
"message": "OneClick! - Все вкладки",
|
||||||
"description": "Menu text"
|
"description": "Menu text"
|
||||||
},
|
},
|
||||||
"dta_turbo_image": {
|
"dta_turbo_image": {
|
||||||
|
File diff suppressed because it is too large
Load Diff
1170
_locales/zh_TW/messages.json
Normal file
1170
_locales/zh_TW/messages.json
Normal file
File diff suppressed because it is too large
Load Diff
396
data/mime.json
Normal file
396
data/mime.json
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
{
|
||||||
|
"e": {
|
||||||
|
"3gpp": "3gp",
|
||||||
|
"asx": "asf",
|
||||||
|
"gz": "gzip",
|
||||||
|
"heic": "heif",
|
||||||
|
"html": [
|
||||||
|
"htm",
|
||||||
|
"shtml",
|
||||||
|
"php"
|
||||||
|
],
|
||||||
|
"jar": [
|
||||||
|
"war",
|
||||||
|
"ear"
|
||||||
|
],
|
||||||
|
"jpg": [
|
||||||
|
"jpeg",
|
||||||
|
"jpe",
|
||||||
|
"jfif"
|
||||||
|
],
|
||||||
|
"js": "jsx",
|
||||||
|
"mid": [
|
||||||
|
"midi",
|
||||||
|
"kar"
|
||||||
|
],
|
||||||
|
"mkv": [
|
||||||
|
"mk3d",
|
||||||
|
"mks"
|
||||||
|
],
|
||||||
|
"mov": [
|
||||||
|
"qt",
|
||||||
|
"moov"
|
||||||
|
],
|
||||||
|
"mpg": [
|
||||||
|
"mpe",
|
||||||
|
"mpeg"
|
||||||
|
],
|
||||||
|
"pem": [
|
||||||
|
"crt",
|
||||||
|
"der"
|
||||||
|
],
|
||||||
|
"pl": "pm",
|
||||||
|
"prc": "pdb",
|
||||||
|
"ps": [
|
||||||
|
"eps",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"svg": "svgz",
|
||||||
|
"tcl": "tk",
|
||||||
|
"tif": "tiff"
|
||||||
|
},
|
||||||
|
"m": {
|
||||||
|
"application/7z": "7z",
|
||||||
|
"application/7z-compressed": "7z",
|
||||||
|
"application/ai": "ps",
|
||||||
|
"application/atom": "atom",
|
||||||
|
"application/atom+xml": "atom",
|
||||||
|
"application/bz2": "bz2",
|
||||||
|
"application/bzip2": "bz2",
|
||||||
|
"application/cco": "cco",
|
||||||
|
"application/cocoa": "cco",
|
||||||
|
"application/compressed": "gz",
|
||||||
|
"application/crt": "pem",
|
||||||
|
"application/der": "pem",
|
||||||
|
"application/doc": "doc",
|
||||||
|
"application/ear": "jar",
|
||||||
|
"application/eot": "eot",
|
||||||
|
"application/eps": "ps",
|
||||||
|
"application/gz": "gz",
|
||||||
|
"application/gzip": "gz",
|
||||||
|
"application/hqx": "hqx",
|
||||||
|
"application/jar": "jar",
|
||||||
|
"application/jardiff": "jardiff",
|
||||||
|
"application/java-archive": "jar",
|
||||||
|
"application/java-archive-diff": "jardiff",
|
||||||
|
"application/java-jnlp-file": "jnlp",
|
||||||
|
"application/javascript": "js",
|
||||||
|
"application/jnlp": "jnlp",
|
||||||
|
"application/js": "js",
|
||||||
|
"application/json": "json",
|
||||||
|
"application/jsx": "js",
|
||||||
|
"application/kml": "kml",
|
||||||
|
"application/kmz": "kmz",
|
||||||
|
"application/m3u8": "m3u8",
|
||||||
|
"application/mac-binhex40": "hqx",
|
||||||
|
"application/makeself": "run",
|
||||||
|
"application/msword": "doc",
|
||||||
|
"application/odg": "odg",
|
||||||
|
"application/odp": "odp",
|
||||||
|
"application/ods": "ods",
|
||||||
|
"application/odt": "odt",
|
||||||
|
"application/pdb": "prc",
|
||||||
|
"application/pdf": "pdf",
|
||||||
|
"application/pem": "pem",
|
||||||
|
"application/perl": "pl",
|
||||||
|
"application/pilot": "prc",
|
||||||
|
"application/pl": "pl",
|
||||||
|
"application/pm": "pl",
|
||||||
|
"application/postscript": "ps",
|
||||||
|
"application/ppt": "ppt",
|
||||||
|
"application/prc": "prc",
|
||||||
|
"application/ps": "ps",
|
||||||
|
"application/rar": "rar",
|
||||||
|
"application/rar-compressed": "rar",
|
||||||
|
"application/redhat-package-manager": "rpm",
|
||||||
|
"application/rpm": "rpm",
|
||||||
|
"application/rss": "rss",
|
||||||
|
"application/rss+xml": "rss",
|
||||||
|
"application/rtf": "rtf",
|
||||||
|
"application/run": "run",
|
||||||
|
"application/sea": "sea",
|
||||||
|
"application/shockwave-flash": "swf",
|
||||||
|
"application/sit": "sit",
|
||||||
|
"application/stuffit": "sit",
|
||||||
|
"application/swf": "swf",
|
||||||
|
"application/tar": "tar",
|
||||||
|
"application/tcl": "tcl",
|
||||||
|
"application/tk": "tcl",
|
||||||
|
"application/vnd.apple.mpegurl": "m3u8",
|
||||||
|
"application/vnd.google-earth.kml+xml": "kml",
|
||||||
|
"application/vnd.google-earth.kmz": "kmz",
|
||||||
|
"application/vnd.ms-excel": "xls",
|
||||||
|
"application/vnd.ms-fontobject": "eot",
|
||||||
|
"application/vnd.ms-powerpoint": "ppt",
|
||||||
|
"application/vnd.oasis.opendocument.graphics": "odg",
|
||||||
|
"application/vnd.oasis.opendocument.presentation": "odp",
|
||||||
|
"application/vnd.oasis.opendocument.spreadsheet": "ods",
|
||||||
|
"application/vnd.oasis.opendocument.text": "odt",
|
||||||
|
"application/vnd.wap.wmlc": "wmlc",
|
||||||
|
"application/war": "jar",
|
||||||
|
"application/wmlc": "wmlc",
|
||||||
|
"application/x-7z": "7z",
|
||||||
|
"application/x-7z-compressed": "7z",
|
||||||
|
"application/x-ai": "ps",
|
||||||
|
"application/x-atom": "atom",
|
||||||
|
"application/x-atom+xml": "atom",
|
||||||
|
"application/x-bz2": "bz2",
|
||||||
|
"application/x-bzip2": "bz2",
|
||||||
|
"application/x-cco": "cco",
|
||||||
|
"application/x-cocoa": "cco",
|
||||||
|
"application/x-compressed": "gz",
|
||||||
|
"application/x-crt": "pem",
|
||||||
|
"application/x-der": "pem",
|
||||||
|
"application/x-doc": "doc",
|
||||||
|
"application/x-ear": "jar",
|
||||||
|
"application/x-eot": "eot",
|
||||||
|
"application/x-eps": "ps",
|
||||||
|
"application/x-gz": "gz",
|
||||||
|
"application/x-gzip": "gz",
|
||||||
|
"application/x-hqx": "hqx",
|
||||||
|
"application/x-jar": "jar",
|
||||||
|
"application/x-jardiff": "jardiff",
|
||||||
|
"application/x-java-archive": "jar",
|
||||||
|
"application/x-java-archive-diff": "jardiff",
|
||||||
|
"application/x-java-jnlp-file": "jnlp",
|
||||||
|
"application/x-javascript": "js",
|
||||||
|
"application/x-jnlp": "jnlp",
|
||||||
|
"application/x-js": "js",
|
||||||
|
"application/x-json": "json",
|
||||||
|
"application/x-jsx": "js",
|
||||||
|
"application/x-kml": "kml",
|
||||||
|
"application/x-kmz": "kmz",
|
||||||
|
"application/x-m3u8": "m3u8",
|
||||||
|
"application/x-mac-binhex40": "hqx",
|
||||||
|
"application/x-makeself": "run",
|
||||||
|
"application/x-msword": "doc",
|
||||||
|
"application/x-odg": "odg",
|
||||||
|
"application/x-odp": "odp",
|
||||||
|
"application/x-ods": "ods",
|
||||||
|
"application/x-odt": "odt",
|
||||||
|
"application/x-pdb": "prc",
|
||||||
|
"application/x-pdf": "pdf",
|
||||||
|
"application/x-pem": "pem",
|
||||||
|
"application/x-perl": "pl",
|
||||||
|
"application/x-pilot": "prc",
|
||||||
|
"application/x-pl": "pl",
|
||||||
|
"application/x-pm": "pl",
|
||||||
|
"application/x-postscript": "ps",
|
||||||
|
"application/x-ppt": "ppt",
|
||||||
|
"application/x-prc": "prc",
|
||||||
|
"application/x-ps": "ps",
|
||||||
|
"application/x-rar": "rar",
|
||||||
|
"application/x-rar-compressed": "rar",
|
||||||
|
"application/x-redhat-package-manager": "rpm",
|
||||||
|
"application/x-rpm": "rpm",
|
||||||
|
"application/x-rss": "rss",
|
||||||
|
"application/x-rss+xml": "rss",
|
||||||
|
"application/x-rtf": "rtf",
|
||||||
|
"application/x-run": "run",
|
||||||
|
"application/x-sea": "sea",
|
||||||
|
"application/x-shockwave-flash": "swf",
|
||||||
|
"application/x-sit": "sit",
|
||||||
|
"application/x-stuffit": "sit",
|
||||||
|
"application/x-swf": "swf",
|
||||||
|
"application/x-tar": "tar",
|
||||||
|
"application/x-tcl": "tcl",
|
||||||
|
"application/x-tk": "tcl",
|
||||||
|
"application/x-vnd.apple.mpegurl": "m3u8",
|
||||||
|
"application/x-vnd.google-earth.kml+xml": "kml",
|
||||||
|
"application/x-vnd.google-earth.kmz": "kmz",
|
||||||
|
"application/x-vnd.ms-excel": "xls",
|
||||||
|
"application/x-vnd.ms-fontobject": "eot",
|
||||||
|
"application/x-vnd.ms-powerpoint": "ppt",
|
||||||
|
"application/x-vnd.oasis.opendocument.graphics": "odg",
|
||||||
|
"application/x-vnd.oasis.opendocument.presentation": "odp",
|
||||||
|
"application/x-vnd.oasis.opendocument.spreadsheet": "ods",
|
||||||
|
"application/x-vnd.oasis.opendocument.text": "odt",
|
||||||
|
"application/x-vnd.wap.wmlc": "wmlc",
|
||||||
|
"application/x-war": "jar",
|
||||||
|
"application/x-wmlc": "wmlc",
|
||||||
|
"application/x-x509-ca-cert": "pem",
|
||||||
|
"application/x-xhtml": "xhtml",
|
||||||
|
"application/x-xhtml+xml": "xhtml",
|
||||||
|
"application/x-xls": "xls",
|
||||||
|
"application/x-xpi": "xpi",
|
||||||
|
"application/x-xpinstall": "xpi",
|
||||||
|
"application/x-xspf": "xspf",
|
||||||
|
"application/x-xspf+xml": "xspf",
|
||||||
|
"application/x-xz": "xz",
|
||||||
|
"application/x-zip": "zip",
|
||||||
|
"application/x509-ca-cert": "pem",
|
||||||
|
"application/xhtml": "xhtml",
|
||||||
|
"application/xhtml+xml": "xhtml",
|
||||||
|
"application/xls": "xls",
|
||||||
|
"application/xpi": "xpi",
|
||||||
|
"application/xpinstall": "xpi",
|
||||||
|
"application/xspf": "xspf",
|
||||||
|
"application/xspf+xml": "xspf",
|
||||||
|
"application/xz": "xz",
|
||||||
|
"application/zip": "zip",
|
||||||
|
"audio/kar": "mid",
|
||||||
|
"audio/m4a": "m4a",
|
||||||
|
"audio/matroska": "mka",
|
||||||
|
"audio/mid": "mid",
|
||||||
|
"audio/midi": "mid",
|
||||||
|
"audio/mka": "mka",
|
||||||
|
"audio/mp3": "mp3",
|
||||||
|
"audio/mpeg": "mp3",
|
||||||
|
"audio/ogg": "ogg",
|
||||||
|
"audio/ra": "ra",
|
||||||
|
"audio/realaudio": "ra",
|
||||||
|
"audio/x-kar": "mid",
|
||||||
|
"audio/x-m4a": "m4a",
|
||||||
|
"audio/x-matroska": "mka",
|
||||||
|
"audio/x-mid": "mid",
|
||||||
|
"audio/x-midi": "mid",
|
||||||
|
"audio/x-mka": "mka",
|
||||||
|
"audio/x-mp3": "mp3",
|
||||||
|
"audio/x-mpeg": "mp3",
|
||||||
|
"audio/x-ogg": "ogg",
|
||||||
|
"audio/x-ra": "ra",
|
||||||
|
"audio/x-realaudio": "ra",
|
||||||
|
"font/woff": "woff",
|
||||||
|
"font/woff2": "woff2",
|
||||||
|
"font/x-woff": "woff",
|
||||||
|
"font/x-woff2": "woff2",
|
||||||
|
"image/bmp": "bmp",
|
||||||
|
"image/gif": "gif",
|
||||||
|
"image/heic": "heic",
|
||||||
|
"image/heif": "heic",
|
||||||
|
"image/heif-sequence": "heic",
|
||||||
|
"image/ico": "ico",
|
||||||
|
"image/icon": "ico",
|
||||||
|
"image/jfif": "jpg",
|
||||||
|
"image/jng": "jng",
|
||||||
|
"image/jpe": "jpg",
|
||||||
|
"image/jpeg": "jpg",
|
||||||
|
"image/jpg": "jpg",
|
||||||
|
"image/ms-bmp": "bmp",
|
||||||
|
"image/png": "png",
|
||||||
|
"image/svg": "svg",
|
||||||
|
"image/svg+xml": "svg",
|
||||||
|
"image/svgz": "svg",
|
||||||
|
"image/tif": "tif",
|
||||||
|
"image/tiff": "tif",
|
||||||
|
"image/vnd.wap.wbmp": "wbmp",
|
||||||
|
"image/wbmp": "wbmp",
|
||||||
|
"image/webp": "webp",
|
||||||
|
"image/x-bmp": "bmp",
|
||||||
|
"image/x-gif": "gif",
|
||||||
|
"image/x-heic": "heic",
|
||||||
|
"image/x-heif": "heic",
|
||||||
|
"image/x-heif-sequence": "heic",
|
||||||
|
"image/x-ico": "ico",
|
||||||
|
"image/x-icon": "ico",
|
||||||
|
"image/x-jfif": "jpg",
|
||||||
|
"image/x-jng": "jng",
|
||||||
|
"image/x-jpe": "jpg",
|
||||||
|
"image/x-jpeg": "jpg",
|
||||||
|
"image/x-jpg": "jpg",
|
||||||
|
"image/x-ms-bmp": "bmp",
|
||||||
|
"image/x-png": "png",
|
||||||
|
"image/x-svg": "svg",
|
||||||
|
"image/x-svg+xml": "svg",
|
||||||
|
"image/x-svgz": "svg",
|
||||||
|
"image/x-tif": "tif",
|
||||||
|
"image/x-tiff": "tif",
|
||||||
|
"image/x-vnd.wap.wbmp": "wbmp",
|
||||||
|
"image/x-wbmp": "wbmp",
|
||||||
|
"image/x-webp": "webp",
|
||||||
|
"text/component": "htc",
|
||||||
|
"text/css": "css",
|
||||||
|
"text/htc": "htc",
|
||||||
|
"text/htm": "html",
|
||||||
|
"text/html": "html",
|
||||||
|
"text/jad": "jad",
|
||||||
|
"text/javascript": "js",
|
||||||
|
"text/js": "js",
|
||||||
|
"text/jsx": "js",
|
||||||
|
"text/mathml": "mml",
|
||||||
|
"text/mml": "mml",
|
||||||
|
"text/php": "html",
|
||||||
|
"text/plain": "txt",
|
||||||
|
"text/shtml": "html",
|
||||||
|
"text/txt": "txt",
|
||||||
|
"text/vnd.sun.j2me.app-descriptor": "jad",
|
||||||
|
"text/vnd.wap.wml": "wml",
|
||||||
|
"text/wml": "wml",
|
||||||
|
"text/x-component": "htc",
|
||||||
|
"text/x-css": "css",
|
||||||
|
"text/x-htc": "htc",
|
||||||
|
"text/x-htm": "html",
|
||||||
|
"text/x-html": "html",
|
||||||
|
"text/x-jad": "jad",
|
||||||
|
"text/x-javascript": "js",
|
||||||
|
"text/x-js": "js",
|
||||||
|
"text/x-jsx": "js",
|
||||||
|
"text/x-mathml": "mml",
|
||||||
|
"text/x-mml": "mml",
|
||||||
|
"text/x-php": "html",
|
||||||
|
"text/x-plain": "txt",
|
||||||
|
"text/x-shtml": "html",
|
||||||
|
"text/x-txt": "txt",
|
||||||
|
"text/x-vnd.sun.j2me.app-descriptor": "jad",
|
||||||
|
"text/x-vnd.wap.wml": "wml",
|
||||||
|
"text/x-wml": "wml",
|
||||||
|
"text/x-xml": "xml",
|
||||||
|
"text/xml": "xml",
|
||||||
|
"video/3gp": "3gpp",
|
||||||
|
"video/3gpp": "3gpp",
|
||||||
|
"video/asf": "asx",
|
||||||
|
"video/asx": "asx",
|
||||||
|
"video/avi": "avi",
|
||||||
|
"video/flv": "flv",
|
||||||
|
"video/m4v": "m4v",
|
||||||
|
"video/matroska": "mkv",
|
||||||
|
"video/mk3d": "mkv",
|
||||||
|
"video/mks": "mkv",
|
||||||
|
"video/mkv": "mkv",
|
||||||
|
"video/mng": "mng",
|
||||||
|
"video/moov": "mov",
|
||||||
|
"video/mov": "mov",
|
||||||
|
"video/mp2t": "ts",
|
||||||
|
"video/mp4": "mp4",
|
||||||
|
"video/mpe": "mpg",
|
||||||
|
"video/mpeg": "mpg",
|
||||||
|
"video/mpg": "mpg",
|
||||||
|
"video/ms-asf": "asx",
|
||||||
|
"video/ms-wmv": "wmv",
|
||||||
|
"video/msvideo": "avi",
|
||||||
|
"video/opus": "opus",
|
||||||
|
"video/qt": "mov",
|
||||||
|
"video/quicktime": "mov",
|
||||||
|
"video/ts": "ts",
|
||||||
|
"video/webm": "webm",
|
||||||
|
"video/wmv": "wmv",
|
||||||
|
"video/x-3gp": "3gpp",
|
||||||
|
"video/x-3gpp": "3gpp",
|
||||||
|
"video/x-asf": "asx",
|
||||||
|
"video/x-asx": "asx",
|
||||||
|
"video/x-avi": "avi",
|
||||||
|
"video/x-flv": "flv",
|
||||||
|
"video/x-m4v": "m4v",
|
||||||
|
"video/x-matroska": "mkv",
|
||||||
|
"video/x-mk3d": "mkv",
|
||||||
|
"video/x-mks": "mkv",
|
||||||
|
"video/x-mkv": "mkv",
|
||||||
|
"video/x-mng": "mng",
|
||||||
|
"video/x-moov": "mov",
|
||||||
|
"video/x-mov": "mov",
|
||||||
|
"video/x-mp2t": "ts",
|
||||||
|
"video/x-mp4": "mp4",
|
||||||
|
"video/x-mpe": "mpg",
|
||||||
|
"video/x-mpeg": "mpg",
|
||||||
|
"video/x-mpg": "mpg",
|
||||||
|
"video/x-ms-asf": "asx",
|
||||||
|
"video/x-ms-wmv": "wmv",
|
||||||
|
"video/x-msvideo": "avi",
|
||||||
|
"video/x-opus": "opus",
|
||||||
|
"video/x-qt": "mov",
|
||||||
|
"video/x-quicktime": "mov",
|
||||||
|
"video/x-ts": "ts",
|
||||||
|
"video/x-webm": "webm",
|
||||||
|
"video/x-wmv": "wmv"
|
||||||
|
}
|
||||||
|
}
|
@ -103,7 +103,7 @@ class Handler {
|
|||||||
discarded: false,
|
discarded: false,
|
||||||
};
|
};
|
||||||
if (!CHROME) {
|
if (!CHROME) {
|
||||||
toptions.hidden = true;
|
toptions.hidden = false;
|
||||||
}
|
}
|
||||||
const selectedTabs = options.allTabs ?
|
const selectedTabs = options.allTabs ?
|
||||||
await tabs.query(toptions) as any[] :
|
await tabs.query(toptions) as any[] :
|
||||||
|
@ -49,6 +49,7 @@ export const {runtime} = polyfill;
|
|||||||
export const {storage} = polyfill;
|
export const {storage} = polyfill;
|
||||||
export const {tabs} = polyfill;
|
export const {tabs} = polyfill;
|
||||||
export const {webNavigation} = polyfill;
|
export const {webNavigation} = polyfill;
|
||||||
|
export const {webRequest} = polyfill;
|
||||||
export const {windows} = polyfill;
|
export const {windows} = polyfill;
|
||||||
|
|
||||||
export const CHROME = navigator.appVersion.includes("Chrome/");
|
export const CHROME = navigator.appVersion.includes("Chrome/");
|
||||||
|
@ -9,7 +9,7 @@ import { EventEmitter } from "./events";
|
|||||||
import { TYPE_LINK, TYPE_MEDIA, TYPE_ALL } from "./constants";
|
import { TYPE_LINK, TYPE_MEDIA, TYPE_ALL } from "./constants";
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { Overlayable } from "./objectoverlay";
|
import { Overlayable } from "./objectoverlay";
|
||||||
import * as DEFAULT_FILTERS from "../data/filters.json";
|
import DEFAULT_FILTERS from "../data/filters.json";
|
||||||
import { FASTFILTER } from "./recentlist";
|
import { FASTFILTER } from "./recentlist";
|
||||||
import { _, locale } from "./i18n";
|
import { _, locale } from "./i18n";
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
// License: MIT
|
// License: MIT
|
||||||
|
|
||||||
import {memoize} from "./memoize";
|
import {memoize} from "./memoize";
|
||||||
import * as langs from "../_locales/all.json";
|
import langs from "../_locales/all.json";
|
||||||
import { sorted, naturalCaseCompare } from "./sorting";
|
import { sorted, naturalCaseCompare } from "./sorting";
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,6 +27,9 @@ const SAVEDPROPS = [
|
|||||||
"written",
|
"written",
|
||||||
// server stuff
|
// server stuff
|
||||||
"serverName",
|
"serverName",
|
||||||
|
"browserName",
|
||||||
|
"mime",
|
||||||
|
"prerolled",
|
||||||
// other options
|
// other options
|
||||||
"private",
|
"private",
|
||||||
// db
|
// db
|
||||||
@ -39,10 +42,13 @@ const DEFAULTS = {
|
|||||||
state: QUEUED,
|
state: QUEUED,
|
||||||
error: "",
|
error: "",
|
||||||
serverName: "",
|
serverName: "",
|
||||||
|
browserName: "",
|
||||||
fileName: "",
|
fileName: "",
|
||||||
totalSize: 0,
|
totalSize: 0,
|
||||||
written: 0,
|
written: 0,
|
||||||
manId: 0,
|
manId: 0,
|
||||||
|
mime: "",
|
||||||
|
prerolled: false
|
||||||
};
|
};
|
||||||
|
|
||||||
let sessionId = 0;
|
let sessionId = 0;
|
||||||
@ -59,14 +65,26 @@ export class BaseDownload {
|
|||||||
|
|
||||||
public url: string;
|
public url: string;
|
||||||
|
|
||||||
|
public usable: string;
|
||||||
|
|
||||||
public uReferrer: URLd;
|
public uReferrer: URLd;
|
||||||
|
|
||||||
public referrer: string;
|
public referrer: string;
|
||||||
|
|
||||||
|
public usableReferrer: string;
|
||||||
|
|
||||||
public startDate: Date;
|
public startDate: Date;
|
||||||
|
|
||||||
public fileName: string;
|
public fileName: string;
|
||||||
|
|
||||||
|
public description?: string;
|
||||||
|
|
||||||
|
public title?: string;
|
||||||
|
|
||||||
|
public batch: number;
|
||||||
|
|
||||||
|
public idx: number;
|
||||||
|
|
||||||
public error: string;
|
public error: string;
|
||||||
|
|
||||||
public postData: any;
|
public postData: any;
|
||||||
@ -79,8 +97,13 @@ export class BaseDownload {
|
|||||||
|
|
||||||
public serverName: string;
|
public serverName: string;
|
||||||
|
|
||||||
|
public browserName: string;
|
||||||
|
|
||||||
|
public mime: string;
|
||||||
|
|
||||||
public mask: string;
|
public mask: string;
|
||||||
|
|
||||||
|
public prerolled: boolean;
|
||||||
|
|
||||||
constructor(options: any) {
|
constructor(options: any) {
|
||||||
Object.assign(this, DEFAULTS);
|
Object.assign(this, DEFAULTS);
|
||||||
@ -115,6 +138,10 @@ export class BaseDownload {
|
|||||||
return this.serverName || this.fileName || this.urlName || "index.html";
|
return this.serverName || this.fileName || this.urlName || "index.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get currentName() {
|
||||||
|
return this.browserName || this.dest.name || this.finalName;
|
||||||
|
}
|
||||||
|
|
||||||
get urlName() {
|
get urlName() {
|
||||||
const path = parsePath(this.uURL);
|
const path = parsePath(this.uURL);
|
||||||
if (path.name) {
|
if (path.name) {
|
||||||
@ -152,6 +179,7 @@ export class BaseDownload {
|
|||||||
rv.destName = dest.name;
|
rv.destName = dest.name;
|
||||||
rv.destPath = dest.path;
|
rv.destPath = dest.path;
|
||||||
rv.destFull = dest.full;
|
rv.destFull = dest.full;
|
||||||
|
rv.currentName = this.browserName || rv.destName || rv.finalName;
|
||||||
rv.error = this.error;
|
rv.error = this.error;
|
||||||
rv.ext = this.renamer.p_ext;
|
rv.ext = this.renamer.p_ext;
|
||||||
return rv;
|
return rv;
|
||||||
|
@ -1,25 +1,70 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
// License: MIT
|
// License: MIT
|
||||||
|
|
||||||
|
import MimeType from "whatwg-mimetype";
|
||||||
|
import { CHROME, downloads, webRequest } from "../browser";
|
||||||
import { Prefs } from "../prefs";
|
import { Prefs } from "../prefs";
|
||||||
import { parsePath, filterInSitu } from "../util";
|
|
||||||
import {
|
|
||||||
QUEUED, RUNNING, CANCELED, PAUSED, MISSING, DONE,
|
|
||||||
FORCABLE, PAUSABLE, CANCELABLE,
|
|
||||||
} from "./state";
|
|
||||||
import { BaseDownload } from "./basedownload";
|
|
||||||
import { PromiseSerializer } from "../pserializer";
|
import { PromiseSerializer } from "../pserializer";
|
||||||
|
import { filterInSitu, parsePath, sanitizePath } from "../util";
|
||||||
|
import { BaseDownload } from "./basedownload";
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { Manager } from "./man";
|
import { Manager } from "./man";
|
||||||
import { downloads, CHROME } from "../browser";
|
import Renamer from "./renamer";
|
||||||
import { debounce } from "../../uikit/lib/util";
|
import {
|
||||||
|
CANCELABLE,
|
||||||
|
CANCELED,
|
||||||
|
DONE,
|
||||||
|
FORCABLE,
|
||||||
|
MISSING,
|
||||||
|
PAUSABLE,
|
||||||
|
PAUSED,
|
||||||
|
QUEUED,
|
||||||
|
RUNNING
|
||||||
|
} from "./state";
|
||||||
|
|
||||||
|
const PREROLL_HEURISTICS = /dl|attach|download|name|file|get|retr|^n$|\.(php|asp|py|pl|action|htm|shtm)/i;
|
||||||
|
const PREROLL_HOSTS = /4cdn|chan/;
|
||||||
|
const PREROLL_TIMEOUT = 10000;
|
||||||
|
const PREROLL_NOPE = new Set<string>();
|
||||||
|
|
||||||
const setShelfEnabled = downloads.setShelfEnabled || function() {
|
function parseDisposition(disp: MimeType) {
|
||||||
// ignored
|
if (!disp) {
|
||||||
};
|
return "";
|
||||||
|
}
|
||||||
|
let encoding = (disp.parameters.get("charset") || "utf-8").trim();
|
||||||
|
let file = (disp.parameters.get("filename") || "").trim().replace(/^(["'])(.*)\1$/, "$2");
|
||||||
|
if (!file) {
|
||||||
|
const encoded = disp.parameters.get("filename*");
|
||||||
|
if (!encoded) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const pieces = encoded.split("'", 3);
|
||||||
|
if (pieces.length !== 3) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
encoding = pieces[0].trim() || encoding;
|
||||||
|
file = (pieces[3] || "").trim().replace(/^(["'])(.*)\1$/, "$2");
|
||||||
|
}
|
||||||
|
file = file.trim();
|
||||||
|
if (!file) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
const reenableShelf = debounce(() => setShelfEnabled(true), 1000, true);
|
try {
|
||||||
|
// And now for the tricky part...
|
||||||
|
// First unescape the string, to get the raw bytes
|
||||||
|
// not utf-8-interpreted bytes
|
||||||
|
// Then convert the string into an uint8[]
|
||||||
|
// Then decode
|
||||||
|
return new TextDecoder(encoding).decode(
|
||||||
|
new Uint8Array(unescape(file).split("").map(e => e.charCodeAt(0)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.error("Cannot decode", encoding, file, ex);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
type Header = {name: string; value: string};
|
type Header = {name: string; value: string};
|
||||||
interface Options {
|
interface Options {
|
||||||
@ -53,6 +98,7 @@ export class Download extends BaseDownload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
markDirty() {
|
markDirty() {
|
||||||
|
this.renamer = new Renamer(this);
|
||||||
this.manager.setDirty(this);
|
this.manager.setDirty(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +126,11 @@ export class Download extends BaseDownload {
|
|||||||
this.updateStateFromBrowser();
|
this.updateStateFromBrowser();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (state[0].state === "complete") {
|
||||||
|
this.changeState(DONE);
|
||||||
|
this.updateStateFromBrowser();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!state[0].canResume) {
|
if (!state[0].canResume) {
|
||||||
throw new Error("Cannot resume");
|
throw new Error("Cannot resume");
|
||||||
}
|
}
|
||||||
@ -97,9 +148,22 @@ export class Download extends BaseDownload {
|
|||||||
if (this.state !== QUEUED) {
|
if (this.state !== QUEUED) {
|
||||||
throw new Error("invalid state");
|
throw new Error("invalid state");
|
||||||
}
|
}
|
||||||
console.trace("starting", this.toString(), this.toMsg());
|
console.log("starting", this.toString(), this.toMsg());
|
||||||
this.changeState(RUNNING);
|
this.changeState(RUNNING);
|
||||||
|
|
||||||
|
// Do NOT await
|
||||||
|
this.reallyStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async reallyStart() {
|
||||||
try {
|
try {
|
||||||
|
if (!this.prerolled) {
|
||||||
|
await this.maybePreroll();
|
||||||
|
if (this.state !== RUNNING) {
|
||||||
|
// Aborted by preroll
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
const options: Options = {
|
const options: Options = {
|
||||||
conflictAction: await Prefs.get("conflict-action"),
|
conflictAction: await Prefs.get("conflict-action"),
|
||||||
filename: this.dest.full,
|
filename: this.dest.full,
|
||||||
@ -124,8 +188,6 @@ export class Download extends BaseDownload {
|
|||||||
this.manager.removeManId(this.manId);
|
this.manager.removeManId(this.manId);
|
||||||
}
|
}
|
||||||
|
|
||||||
setShelfEnabled(false);
|
|
||||||
try {
|
|
||||||
try {
|
try {
|
||||||
this.manager.addManId(
|
this.manager.addManId(
|
||||||
this.manId = await downloads.download(options), this);
|
this.manId = await downloads.download(options), this);
|
||||||
@ -139,10 +201,6 @@ export class Download extends BaseDownload {
|
|||||||
this.manager.addManId(
|
this.manager.addManId(
|
||||||
this.manId = await downloads.download(options), this);
|
this.manId = await downloads.download(options), this);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally {
|
|
||||||
reenableShelf();
|
|
||||||
}
|
|
||||||
this.markDirty();
|
this.markDirty();
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
@ -152,6 +210,146 @@ export class Download extends BaseDownload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get shouldPreroll() {
|
||||||
|
const {pathname, search, host} = this.uURL;
|
||||||
|
if (PREROLL_NOPE.has(host)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.renamer.p_ext) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (search.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this.uURL.pathname.endsWith("/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (PREROLL_HEURISTICS.test(pathname)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (PREROLL_HOSTS.test(host)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async maybePreroll() {
|
||||||
|
try {
|
||||||
|
if (this.prerolled) {
|
||||||
|
// Check again, just in case, async and all
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.shouldPreroll) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await (CHROME ? this.prerollChrome() : this.prerollFirefox());
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
console.error("Failed to preroll", this, ex.toString(), ex.stack, ex);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (this.state === RUNNING) {
|
||||||
|
this.prerolled = true;
|
||||||
|
this.markDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async prerollFirefox() {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const {signal} = controller;
|
||||||
|
const res = await fetch(this.uURL.toString(), {
|
||||||
|
method: "HEAD",
|
||||||
|
mode: "same-origin",
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
controller.abort();
|
||||||
|
const {headers} = res;
|
||||||
|
this.prerollFinialize(headers, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
async prerollChrome() {
|
||||||
|
let rid = "";
|
||||||
|
const rurl = this.uURL.toString();
|
||||||
|
let listener: any;
|
||||||
|
const wr = new Promise<any[]>(resolve => {
|
||||||
|
listener = (details: any) => {
|
||||||
|
const {url, requestId, statusCode} = details;
|
||||||
|
if (rid !== requestId && url !== rurl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-magic-numbers
|
||||||
|
if (statusCode >= 300 && statusCode < 400) {
|
||||||
|
// Redirect, continue tracking;
|
||||||
|
rid = requestId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(details.responseHeaders);
|
||||||
|
};
|
||||||
|
webRequest.onHeadersReceived.addListener(
|
||||||
|
listener, {urls: ["<all_urls>"]}, ["responseHeaders"]);
|
||||||
|
});
|
||||||
|
const p = Promise.race([
|
||||||
|
wr,
|
||||||
|
new Promise<any[]>((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error("timeout")), PREROLL_TIMEOUT))
|
||||||
|
]);
|
||||||
|
|
||||||
|
p.finally(() => {
|
||||||
|
webRequest.onHeadersReceived.removeListener(listener);
|
||||||
|
});
|
||||||
|
const controller = new AbortController();
|
||||||
|
const {signal} = controller;
|
||||||
|
const res = await fetch(rurl, {
|
||||||
|
method: "HEAD",
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
controller.abort();
|
||||||
|
const headers = await p;
|
||||||
|
this.prerollFinialize(
|
||||||
|
new Headers(headers.map(i => [i.name, i.value])), res);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private prerollFinialize(headers: Headers, res: Response) {
|
||||||
|
const type = MimeType.parse(headers.get("content-type") || "");
|
||||||
|
const dispHeader = headers.get("content-disposition");
|
||||||
|
let file = "";
|
||||||
|
if (dispHeader) {
|
||||||
|
const disp = new MimeType(`${type && type.toString() || "application/octet-stream"}; ${dispHeader}`);
|
||||||
|
file = parseDisposition(disp);
|
||||||
|
// Sanitize
|
||||||
|
file = sanitizePath(file.replace(/[/\\]+/g, "-"));
|
||||||
|
}
|
||||||
|
if (type) {
|
||||||
|
this.mime = type.essence;
|
||||||
|
}
|
||||||
|
this.serverName = file;
|
||||||
|
this.markDirty();
|
||||||
|
const {status} = res;
|
||||||
|
/* eslint-disable no-magic-numbers */
|
||||||
|
if (status === 404) {
|
||||||
|
this.cancel();
|
||||||
|
this.error = "SERVER_BAD_CONTENT";
|
||||||
|
}
|
||||||
|
else if (status === 403) {
|
||||||
|
this.cancel();
|
||||||
|
this.error = "SERVER_FORBIDDEN";
|
||||||
|
}
|
||||||
|
else if (status === 402 || status === 407) {
|
||||||
|
this.cancel();
|
||||||
|
this.error = "SERVER_UNAUTHORIZED";
|
||||||
|
}
|
||||||
|
else if (status === 400 || status === 405) {
|
||||||
|
PREROLL_NOPE.add(this.uURL.host);
|
||||||
|
}
|
||||||
|
else if (status > 400 && status < 500) {
|
||||||
|
this.cancel();
|
||||||
|
this.error = "SERVER_FAILED";
|
||||||
|
}
|
||||||
|
/* eslint-enable no-magic-numbers */
|
||||||
|
}
|
||||||
|
|
||||||
resume(forced = false) {
|
resume(forced = false) {
|
||||||
if (!(FORCABLE & this.state)) {
|
if (!(FORCABLE & this.state)) {
|
||||||
return;
|
return;
|
||||||
@ -181,9 +379,10 @@ export class Download extends BaseDownload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
|
this.prerolled = false;
|
||||||
this.manId = 0;
|
this.manId = 0;
|
||||||
this.written = this.totalSize = 0;
|
this.written = this.totalSize = 0;
|
||||||
this.serverName = "";
|
this.mime = this.serverName = this.browserName = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeFromBrowser() {
|
async removeFromBrowser() {
|
||||||
@ -260,8 +459,11 @@ export class Download extends BaseDownload {
|
|||||||
const state = (await downloads.search({id: this.manId})).pop();
|
const state = (await downloads.search({id: this.manId})).pop();
|
||||||
const {filename, error} = state;
|
const {filename, error} = state;
|
||||||
const path = parsePath(filename);
|
const path = parsePath(filename);
|
||||||
this.serverName = path.name;
|
this.browserName = path.name;
|
||||||
this.adoptSize(state);
|
this.adoptSize(state);
|
||||||
|
if (!this.mime && state.mime) {
|
||||||
|
this.mime = state.mime;
|
||||||
|
}
|
||||||
this.markDirty();
|
this.markDirty();
|
||||||
switch (state.state) {
|
switch (state.state) {
|
||||||
case "in_progress":
|
case "in_progress":
|
||||||
|
@ -25,11 +25,14 @@ const DIRTY_TIMEOUT = 100;
|
|||||||
const MISSING_TIMEOUT = 12 * 1000;
|
const MISSING_TIMEOUT = 12 * 1000;
|
||||||
const RELOAD_TIMEOUT = 10 * 1000;
|
const RELOAD_TIMEOUT = 10 * 1000;
|
||||||
|
|
||||||
|
const setShelfEnabled = downloads.setShelfEnabled || function() {
|
||||||
|
// ignored
|
||||||
|
};
|
||||||
|
|
||||||
export class Manager extends EventEmitter {
|
export class Manager extends EventEmitter {
|
||||||
private items: Download[];
|
private items: Download[];
|
||||||
|
|
||||||
private active: boolean;
|
public active: boolean;
|
||||||
|
|
||||||
private notifiedFinished: boolean;
|
private notifiedFinished: boolean;
|
||||||
|
|
||||||
@ -93,7 +96,10 @@ export class Manager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.items.push(rv);
|
this.items.push(rv);
|
||||||
});
|
});
|
||||||
await this.resetScheduler();
|
|
||||||
|
// Do not wait for the scheduler
|
||||||
|
this.resetScheduler();
|
||||||
|
|
||||||
this.emit("inited");
|
this.emit("inited");
|
||||||
setTimeout(() => this.checkMissing(), MISSING_TIMEOUT);
|
setTimeout(() => this.checkMissing(), MISSING_TIMEOUT);
|
||||||
runtime.onUpdateAvailable.addListener(() => {
|
runtime.onUpdateAvailable.addListener(() => {
|
||||||
@ -148,7 +154,7 @@ export class Manager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
const next = await this.scheduler.next(this.running);
|
const next = await this.scheduler.next(this.running);
|
||||||
if (!next) {
|
if (!next) {
|
||||||
this.maybeNotifyFinished();
|
this.maybeRunFinishActions();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this.running.has(next) || next.state !== QUEUED) {
|
if (this.running.has(next) || next.state !== QUEUED) {
|
||||||
@ -168,10 +174,31 @@ export class Manager extends EventEmitter {
|
|||||||
async startDownload(download: Download) {
|
async startDownload(download: Download) {
|
||||||
// Add to running first, so we don't confuse the scheduler and other parts
|
// Add to running first, so we don't confuse the scheduler and other parts
|
||||||
this.running.add(download);
|
this.running.add(download);
|
||||||
|
setShelfEnabled(false);
|
||||||
await download.start();
|
await download.start();
|
||||||
this.notifiedFinished = false;
|
this.notifiedFinished = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async maybeRunFinishActions() {
|
||||||
|
if (this.running.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.maybeNotifyFinished();
|
||||||
|
if (this.running.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.shouldReload) {
|
||||||
|
this.saveQueue.trigger();
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.running.size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runtime.reload();
|
||||||
|
}, RELOAD_TIMEOUT);
|
||||||
|
}
|
||||||
|
setShelfEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
async maybeNotifyFinished() {
|
async maybeNotifyFinished() {
|
||||||
if (!(await Prefs.get("finish-notification"))) {
|
if (!(await Prefs.get("finish-notification"))) {
|
||||||
return;
|
return;
|
||||||
@ -181,14 +208,6 @@ export class Manager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.notifiedFinished = true;
|
this.notifiedFinished = true;
|
||||||
new Notification(null, _("queue-finished"));
|
new Notification(null, _("queue-finished"));
|
||||||
if (this.shouldReload) {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.running.size) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
runtime.reload();
|
|
||||||
}, RELOAD_TIMEOUT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addManId(id: number, download: Download) {
|
addManId(id: number, download: Download) {
|
||||||
@ -236,7 +255,7 @@ export class Manager extends EventEmitter {
|
|||||||
this.emit("dirty", items);
|
this.emit("dirty", items);
|
||||||
}
|
}
|
||||||
|
|
||||||
save(items: Download[]) {
|
private save(items: Download[]) {
|
||||||
DB.saveItems(items.filter(i => !i.removed)).
|
DB.saveItems(items.filter(i => !i.removed)).
|
||||||
catch(console.error);
|
catch(console.error);
|
||||||
}
|
}
|
||||||
@ -361,6 +380,10 @@ export class Manager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.emit("active", this.active);
|
this.emit("active", this.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMsgItems() {
|
||||||
|
return this.items.map(e => e.toMsg());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let inited: Promise<Manager>;
|
let inited: Promise<Manager>;
|
||||||
|
@ -5,6 +5,10 @@ import { donate, openPrefs } from "../windowutils";
|
|||||||
import { API } from "../api";
|
import { API } from "../api";
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
import { BaseDownload } from "./basedownload";
|
import { BaseDownload } from "./basedownload";
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { Manager } from "./man";
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { Port } from "../bus";
|
||||||
|
|
||||||
type SID = {sid: number};
|
type SID = {sid: number};
|
||||||
type SIDS = {
|
type SIDS = {
|
||||||
@ -13,9 +17,9 @@ type SIDS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class ManagerPort {
|
export class ManagerPort {
|
||||||
private manager: any;
|
private manager: Manager;
|
||||||
|
|
||||||
private port: any;
|
private port: Port;
|
||||||
|
|
||||||
constructor(manager: any, port: any) {
|
constructor(manager: any, port: any) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
@ -79,7 +83,6 @@ export class ManagerPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendAll() {
|
sendAll() {
|
||||||
this.port.post(
|
this.port.post("all", this.manager.getMsgItems());
|
||||||
"all", this.manager.items.map((e: BaseDownload) => e.toMsg()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,12 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
// License: MIT
|
// License: MIT
|
||||||
|
|
||||||
import { parsePath, sanitizePath } from "../util";
|
|
||||||
import { _ } from "../i18n";
|
import { _ } from "../i18n";
|
||||||
|
import { MimeDB } from "../mime";
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { parsePath, PathInfo, sanitizePath } from "../util";
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import { BaseDownload } from "./basedownload";
|
||||||
|
|
||||||
const REPLACE_EXPR = /\*\w+\*/gi;
|
const REPLACE_EXPR = /\*\w+\*/gi;
|
||||||
|
|
||||||
@ -22,21 +26,41 @@ const DATE_FORMATTER = new Intl.NumberFormat(undefined, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default class Renamer {
|
export default class Renamer {
|
||||||
private readonly d: any;
|
private readonly d: BaseDownload;
|
||||||
|
|
||||||
constructor(download: any) {
|
private readonly nameinfo: PathInfo;
|
||||||
|
|
||||||
|
constructor(download: BaseDownload) {
|
||||||
this.d = download;
|
this.d = download;
|
||||||
|
const info = parsePath(this.d.finalName);
|
||||||
|
this.nameinfo = this.fixupExtension(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
get nameinfo() {
|
private fixupExtension(info: PathInfo): PathInfo {
|
||||||
return parsePath(this.d.finalName);
|
if (!this.d.mime) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
const mime = MimeDB.getMime(this.d.mime);
|
||||||
|
if (!mime) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
const {ext} = info;
|
||||||
|
if (mime.major === "image" || mime.major === "video") {
|
||||||
|
if (ext && mime.extensions.has(ext.toLowerCase())) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
return new PathInfo(info.base, mime.primary, info.path);
|
||||||
|
}
|
||||||
|
if (ext) {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
return new PathInfo(info.base, mime.primary, info.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
get ref() {
|
get ref() {
|
||||||
return this.d.uReferrer;
|
return this.d.uReferrer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
get p_name() {
|
get p_name() {
|
||||||
return this.nameinfo.base;
|
return this.nameinfo.base;
|
||||||
}
|
}
|
||||||
@ -184,7 +208,7 @@ export default class Renamer {
|
|||||||
(self[prop] || "").trim() :
|
(self[prop] || "").trim() :
|
||||||
type;
|
type;
|
||||||
if (flat) {
|
if (flat) {
|
||||||
return rv.replace(/\/+/g, "-");
|
return rv.replace(/[/\\]+/g, "-");
|
||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
||||||
}));
|
}));
|
||||||
|
55
lib/mime.ts
Normal file
55
lib/mime.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
"use strict";
|
||||||
|
// License: MIT
|
||||||
|
|
||||||
|
import mime from "../data/mime.json";
|
||||||
|
|
||||||
|
export class MimeInfo {
|
||||||
|
public readonly type: string;
|
||||||
|
|
||||||
|
public readonly extensions: Set<string>;
|
||||||
|
|
||||||
|
public readonly major: string;
|
||||||
|
|
||||||
|
public readonly minor: string;
|
||||||
|
|
||||||
|
public readonly primary: string;
|
||||||
|
|
||||||
|
constructor(type: string, extensions: string[]) {
|
||||||
|
this.type = type;
|
||||||
|
const [major, minor] = type.split("/", 2);
|
||||||
|
this.major = major;
|
||||||
|
this.minor = minor;
|
||||||
|
[this.primary] = extensions;
|
||||||
|
this.extensions = new Set(extensions);
|
||||||
|
Object.freeze(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MimeDB = new class {
|
||||||
|
private readonly mimeToExts: Map<string, MimeInfo>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const exts = new Map<string, string[]>();
|
||||||
|
for (const [prim, more] of Object.entries(mime.e)) {
|
||||||
|
let toadd = more;
|
||||||
|
if (!Array.isArray(toadd)) {
|
||||||
|
toadd = [toadd];
|
||||||
|
}
|
||||||
|
toadd.unshift(prim);
|
||||||
|
exts.set(prim, toadd);
|
||||||
|
}
|
||||||
|
this.mimeToExts = new Map(Array.from(
|
||||||
|
Object.entries(mime.m),
|
||||||
|
([mime, prim]) => [mime, new MimeInfo(mime, exts.get(prim) || [prim])]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrimary(mime: string) {
|
||||||
|
const info = this.mimeToExts.get(mime.trim().toLocaleLowerCase());
|
||||||
|
return info ? info.primary : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
getMime(mime: string) {
|
||||||
|
return this.mimeToExts.get(mime.trim().toLocaleLowerCase());
|
||||||
|
}
|
||||||
|
}();
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
// License: MIT
|
// License: MIT
|
||||||
|
|
||||||
import * as DEFAULT_PREFS from "../data/prefs.json";
|
import DEFAULT_PREFS from "../data/prefs.json";
|
||||||
import { EventEmitter } from "./events";
|
import { EventEmitter } from "./events";
|
||||||
import { loadOverlay } from "./objectoverlay";
|
import { loadOverlay } from "./objectoverlay";
|
||||||
import { storage } from "./browser";
|
import { storage } from "./browser";
|
||||||
|
76
lib/util.ts
76
lib/util.ts
@ -2,7 +2,7 @@
|
|||||||
// License: MIT
|
// License: MIT
|
||||||
|
|
||||||
import * as psl from "psl";
|
import * as psl from "psl";
|
||||||
import {memoize, identity} from "./memoize";
|
import { identity, memoize } from "./memoize";
|
||||||
export { debounce } from "../uikit/lib/util";
|
export { debounce } from "../uikit/lib/util";
|
||||||
|
|
||||||
export class Promised {
|
export class Promised {
|
||||||
@ -96,8 +96,72 @@ export const IS_WIN = typeof navigator !== "undefined" &&
|
|||||||
export const sanitizePath = identity(
|
export const sanitizePath = identity(
|
||||||
IS_WIN ? sanitizePathWindows : sanitizePathGeneric);
|
IS_WIN ? sanitizePathWindows : sanitizePathGeneric);
|
||||||
|
|
||||||
|
export class PathInfo {
|
||||||
|
private baseField: string;
|
||||||
|
|
||||||
|
private extField: string;
|
||||||
|
|
||||||
|
private pathField: string;
|
||||||
|
|
||||||
|
private nameField: string;
|
||||||
|
|
||||||
|
private fullField: string;
|
||||||
|
|
||||||
|
constructor(base: string, ext: string, path: string) {
|
||||||
|
this.baseField = base;
|
||||||
|
this.extField = ext;
|
||||||
|
this.pathField = path;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
get base() {
|
||||||
|
return this.baseField;
|
||||||
|
}
|
||||||
|
|
||||||
|
set base(nv) {
|
||||||
|
this.baseField = sanitizePath(nv);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
get ext() {
|
||||||
|
return this.extField;
|
||||||
|
}
|
||||||
|
|
||||||
|
set ext(nv) {
|
||||||
|
this.extField = sanitizePath(nv);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
get name() {
|
||||||
|
return this.nameField;
|
||||||
|
}
|
||||||
|
|
||||||
|
get path() {
|
||||||
|
return this.pathField;
|
||||||
|
}
|
||||||
|
|
||||||
|
set path(nv) {
|
||||||
|
this.pathField = sanitizePath(nv);
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
get full() {
|
||||||
|
return this.fullField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
this.nameField = this.extField ? `${this.baseField}.${this.extField}` : this.baseField;
|
||||||
|
this.fullField = this.pathField ? `${this.pathField}/${this.nameField}` : this.nameField;
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new PathInfo(this.baseField, this.extField, this.pathField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// XXX cleanup + test
|
// XXX cleanup + test
|
||||||
export const parsePath = memoize(function parsePath(path: string | URL) {
|
export const parsePath = memoize(function parsePath(
|
||||||
|
path: string | URL): PathInfo {
|
||||||
if (path instanceof URL) {
|
if (path instanceof URL) {
|
||||||
path = decodeURIComponent(path.pathname);
|
path = decodeURIComponent(path.pathname);
|
||||||
}
|
}
|
||||||
@ -127,13 +191,7 @@ export const parsePath = memoize(function parsePath(path: string | URL) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
path = pieces.join("/");
|
path = pieces.join("/");
|
||||||
return {
|
return new PathInfo(base, ext, path);
|
||||||
path,
|
|
||||||
name,
|
|
||||||
base,
|
|
||||||
ext,
|
|
||||||
full: path ? `${path}/${name}` : name
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export class CoalescedUpdate<T> extends Set<T> {
|
export class CoalescedUpdate<T> extends Set<T> {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import { windows, tabs, runtime } from "../lib/browser";
|
import { windows, tabs, runtime } from "../lib/browser";
|
||||||
import {getManager} from "./manager/man";
|
import {getManager} from "./manager/man";
|
||||||
import * as DEFAULT_ICONS from "../data/icons.json";
|
import DEFAULT_ICONS from "../data/icons.json";
|
||||||
|
|
||||||
const DONATE_URL = "https://www.downthemall.org/howto/donate/";
|
const DONATE_URL = "https://www.downthemall.org/howto/donate/";
|
||||||
const MANAGER_URL = "/windows/manager.html";
|
const MANAGER_URL = "/windows/manager.html";
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "DownThemAll!",
|
"name": "DownThemAll!",
|
||||||
"version": "4.0.8",
|
"version": "4.0.9",
|
||||||
|
|
||||||
"description": "__MSG_extensionDescription__",
|
"description": "__MSG_extensionDescription__",
|
||||||
"homepage_url": "https://downthemall.org/",
|
"homepage_url": "https://downthemall.org/",
|
||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
|
|
||||||
"content_security_policy": "script-src 'self'; style-src 'self' 'unsafe-inline'; img-src data: blob: 'self'; connect-src data: blob: 'self'; default-src 'self'",
|
"content_security_policy": "script-src 'self'; style-src 'self' 'unsafe-inline'; img-src data: blob: 'self'; connect-src data: blob: http: https: 'self'; default-src 'self'",
|
||||||
|
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "style/icon16.png",
|
"16": "style/icon16.png",
|
||||||
@ -31,7 +31,8 @@
|
|||||||
"notifications",
|
"notifications",
|
||||||
"storage",
|
"storage",
|
||||||
"tabs",
|
"tabs",
|
||||||
"webNavigation"
|
"webNavigation",
|
||||||
|
"webRequest"
|
||||||
],
|
],
|
||||||
|
|
||||||
"background": {
|
"background": {
|
||||||
|
@ -33,7 +33,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/psl": "^1.1.0",
|
"@types/psl": "^1.1.0",
|
||||||
|
"@types/whatwg-mimetype": "^2.1.0",
|
||||||
"psl": "^1.3.0",
|
"psl": "^1.3.0",
|
||||||
"webextension-polyfill": "^0.4.0"
|
"webextension-polyfill": "^0.4.0",
|
||||||
|
"whatwg-mimetype": "^2.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -295,7 +295,7 @@ class Gatherer {
|
|||||||
function gather(msg: any, sender: any, callback: Function) {
|
function gather(msg: any, sender: any, callback: Function) {
|
||||||
try {
|
try {
|
||||||
if (!msg || msg.type !== "DTA:gather" || !callback) {
|
if (!msg || msg.type !== "DTA:gather" || !callback) {
|
||||||
return;
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
const gatherer = new Gatherer(msg);
|
const gatherer = new Gatherer(msg);
|
||||||
const result = {
|
const result = {
|
||||||
@ -313,10 +313,11 @@ function gather(msg: any, sender: any, callback: Function) {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
urlToUsable(result, result.baseURL);
|
urlToUsable(result, result.baseURL);
|
||||||
callback(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
console.error(ex.toString(), ex.stack, ex);
|
console.error(ex.toString(), ex.stack, ex);
|
||||||
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
--folder-color: rgb(214, 165, 4);
|
--folder-color: rgb(214, 165, 4);
|
||||||
--maskbutton-color: rgb(236, 185, 16);
|
--maskbutton-color: rgb(236, 185, 16);
|
||||||
--missing-color: rgb(0, 82, 204);
|
--missing-color: rgb(0, 82, 204);
|
||||||
|
--open-color: rgba(236, 185, 16, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
html[data-platform="mac"] {
|
html[data-platform="mac"] {
|
||||||
|
@ -108,11 +108,11 @@ body > * {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#colURL {
|
#colURL {
|
||||||
width: 38%;
|
width: 42%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#colPercent {
|
#colPercent {
|
||||||
width: 3em;
|
width: 4em;
|
||||||
min-width: 3em;
|
min-width: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,11 +121,11 @@ body > * {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#colSize {
|
#colSize {
|
||||||
width: 15em;
|
width: 14em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#colSpeed {
|
#colSpeed {
|
||||||
width: 6em;
|
width: 7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#colDomain,
|
#colDomain,
|
||||||
@ -154,6 +154,14 @@ body > * {
|
|||||||
height: 26px;
|
height: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.virtualtable-row.opening {
|
||||||
|
background: var(--open-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.virtualtable-progress-container {
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
.virtualtable-progress-bar {
|
.virtualtable-progress-bar {
|
||||||
height: 14px;
|
height: 14px;
|
||||||
}
|
}
|
||||||
@ -262,6 +270,7 @@ body > * {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.virtualtable-column-6,
|
.virtualtable-column-6,
|
||||||
|
.virtualtable-column-4,
|
||||||
.virtualtable-column-3 {
|
.virtualtable-column-3 {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
@ -430,6 +439,8 @@ body > * {
|
|||||||
justify-items: stretch;
|
justify-items: stretch;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 2px 2px 6px black;
|
box-shadow: 2px 2px 6px black;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#tooltip-infos {
|
#tooltip-infos {
|
||||||
|
@ -232,3 +232,7 @@ body > * {
|
|||||||
#maskButton {
|
#maskButton {
|
||||||
justify-self: flex-start;
|
justify-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#btnDownload {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@ -81,3 +81,7 @@ h3 {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#btnDownload {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
@ -19,7 +19,7 @@ const OPTS = {
|
|||||||
state: DownloadState.QUEUED,
|
state: DownloadState.QUEUED,
|
||||||
batch: 42,
|
batch: 42,
|
||||||
idx: 23,
|
idx: 23,
|
||||||
mask: "*name*.*ext",
|
mask: "*name*.*ext*",
|
||||||
description: "desc / ript.ion .",
|
description: "desc / ript.ion .",
|
||||||
title: " *** TITLE *** ",
|
title: " *** TITLE *** ",
|
||||||
};
|
};
|
||||||
@ -57,6 +57,49 @@ describe("Renamer", function() {
|
|||||||
expect(dest.path).to.equal("");
|
expect(dest.path).to.equal("");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("*name*.*ext* (mime override)", function() {
|
||||||
|
const {dest} = new BaseDownload(
|
||||||
|
Object.assign({}, OPTS, {
|
||||||
|
mask: "*name* *batch*.*ext*",
|
||||||
|
mime: "image/jpeg"
|
||||||
|
}));
|
||||||
|
expect(dest.full).to.equal("filenäme 042.jpg");
|
||||||
|
expect(dest.name).to.equal("filenäme 042.jpg");
|
||||||
|
expect(dest.base).to.equal("filenäme 042");
|
||||||
|
expect(dest.ext).to.equal("jpg");
|
||||||
|
expect(dest.path).to.equal("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("*name*.*ext* (mime no override)", function() {
|
||||||
|
const {dest} = new BaseDownload(
|
||||||
|
Object.assign({}, OPTS, {
|
||||||
|
mask: "*name* *batch*.*ext*",
|
||||||
|
mime: "image/jpeg",
|
||||||
|
url: "https://www.example.co.uk/filen%C3%A4me.JPe",
|
||||||
|
usable: "https://www.example.co.uk/filenäme.JPe",
|
||||||
|
}));
|
||||||
|
expect(dest.full).to.equal("filenäme 042.JPe");
|
||||||
|
expect(dest.name).to.equal("filenäme 042.JPe");
|
||||||
|
expect(dest.base).to.equal("filenäme 042");
|
||||||
|
expect(dest.ext).to.equal("JPe");
|
||||||
|
expect(dest.path).to.equal("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("*name*.*ext* (mime override; missing ext)", function() {
|
||||||
|
const {dest} = new BaseDownload(
|
||||||
|
Object.assign({}, OPTS, {
|
||||||
|
mask: "*name* *batch*.*ext*",
|
||||||
|
mime: "application/json",
|
||||||
|
url: "https://www.example.co.uk/filen%C3%A4me",
|
||||||
|
usable: "https://www.example.co.uk/filenäme",
|
||||||
|
}));
|
||||||
|
expect(dest.full).to.equal("filenäme 042.json");
|
||||||
|
expect(dest.name).to.equal("filenäme 042.json");
|
||||||
|
expect(dest.base).to.equal("filenäme 042");
|
||||||
|
expect(dest.ext).to.equal("json");
|
||||||
|
expect(dest.path).to.equal("");
|
||||||
|
});
|
||||||
|
|
||||||
it("*text*", function() {
|
it("*text*", function() {
|
||||||
const dest = makeOne("*text*");
|
const dest = makeOne("*text*");
|
||||||
expect(dest.full).to.equal("desc/ript.ion");
|
expect(dest.full).to.equal("desc/ript.ion");
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"importHelpers": true,
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
util/additional.types
Normal file
23
util/additional.types
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
types {
|
||||||
|
application/x-x509-ca-cert pem crt der;
|
||||||
|
application/javascript js jsx;
|
||||||
|
audio/x-matroska mka;
|
||||||
|
image/bmp bmp;
|
||||||
|
image/heic heic heif;
|
||||||
|
image/heic heic heif;
|
||||||
|
image/heif-sequence heic heif;
|
||||||
|
image/heif-sequence heic heif;
|
||||||
|
image/jpeg jpg jpeg jpe jfif;
|
||||||
|
image/webp webp;
|
||||||
|
text/html html htm shtml php;
|
||||||
|
text/javascript js jsx;
|
||||||
|
video/mpeg mpg mpe mpeg mpg;
|
||||||
|
video/opus opus;
|
||||||
|
video/x-matroska mkv mk3d mks;
|
||||||
|
video/quicktime mov qt moov;
|
||||||
|
application/x-compressed gz;
|
||||||
|
application/x-gzip gz gzip;
|
||||||
|
application/x-bzip2 bz2;
|
||||||
|
application/x-tar tar;
|
||||||
|
application/x-xz xz;
|
||||||
|
}
|
@ -26,7 +26,7 @@ UNCOMPRESSABLE = set((".png", ".jpg", ".zip", ".woff2"))
|
|||||||
LICENSED = set((".css", ".html", ".js", "*.ts"))
|
LICENSED = set((".css", ".html", ".js", "*.ts"))
|
||||||
IGNORED = set((".DS_Store", "Thumbs.db"))
|
IGNORED = set((".DS_Store", "Thumbs.db"))
|
||||||
|
|
||||||
PERM_IGNORED_FX = set(("downloads.shelf",))
|
PERM_IGNORED_FX = set(("downloads.shelf", "webRequest"))
|
||||||
PERM_IGNORED_CHROME = set(("menus",))
|
PERM_IGNORED_CHROME = set(("menus",))
|
||||||
|
|
||||||
SCRIPTS = [
|
SCRIPTS = [
|
||||||
|
98
util/mime.types
Normal file
98
util/mime.types
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
https://github.com/nginx/nginx/raw/master/conf/mime.types
|
||||||
|
|
||||||
|
types {
|
||||||
|
text/html html htm shtml;
|
||||||
|
text/css css;
|
||||||
|
text/xml xml;
|
||||||
|
image/gif gif;
|
||||||
|
image/jpeg jpeg jpg;
|
||||||
|
application/javascript js;
|
||||||
|
application/atom+xml atom;
|
||||||
|
application/rss+xml rss;
|
||||||
|
|
||||||
|
text/mathml mml;
|
||||||
|
text/plain txt;
|
||||||
|
text/vnd.sun.j2me.app-descriptor jad;
|
||||||
|
text/vnd.wap.wml wml;
|
||||||
|
text/x-component htc;
|
||||||
|
|
||||||
|
image/png png;
|
||||||
|
image/svg+xml svg svgz;
|
||||||
|
image/tiff tif tiff;
|
||||||
|
image/vnd.wap.wbmp wbmp;
|
||||||
|
image/webp webp;
|
||||||
|
image/x-icon ico;
|
||||||
|
image/x-jng jng;
|
||||||
|
image/x-ms-bmp bmp;
|
||||||
|
|
||||||
|
font/woff woff;
|
||||||
|
font/woff2 woff2;
|
||||||
|
|
||||||
|
application/java-archive jar war ear;
|
||||||
|
application/json json;
|
||||||
|
application/mac-binhex40 hqx;
|
||||||
|
application/msword doc;
|
||||||
|
application/pdf pdf;
|
||||||
|
application/postscript ps eps ai;
|
||||||
|
application/rtf rtf;
|
||||||
|
application/vnd.apple.mpegurl m3u8;
|
||||||
|
application/vnd.google-earth.kml+xml kml;
|
||||||
|
application/vnd.google-earth.kmz kmz;
|
||||||
|
application/vnd.ms-excel xls;
|
||||||
|
application/vnd.ms-fontobject eot;
|
||||||
|
application/vnd.ms-powerpoint ppt;
|
||||||
|
application/vnd.oasis.opendocument.graphics odg;
|
||||||
|
application/vnd.oasis.opendocument.presentation odp;
|
||||||
|
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||||
|
application/vnd.oasis.opendocument.text odt;
|
||||||
|
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||||
|
pptx;
|
||||||
|
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
xlsx;
|
||||||
|
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||||
|
docx;
|
||||||
|
application/vnd.wap.wmlc wmlc;
|
||||||
|
application/x-7z-compressed 7z;
|
||||||
|
application/x-cocoa cco;
|
||||||
|
application/x-java-archive-diff jardiff;
|
||||||
|
application/x-java-jnlp-file jnlp;
|
||||||
|
application/x-makeself run;
|
||||||
|
application/x-perl pl pm;
|
||||||
|
application/x-pilot prc pdb;
|
||||||
|
application/x-rar-compressed rar;
|
||||||
|
application/x-redhat-package-manager rpm;
|
||||||
|
application/x-sea sea;
|
||||||
|
application/x-shockwave-flash swf;
|
||||||
|
application/x-stuffit sit;
|
||||||
|
application/x-tcl tcl tk;
|
||||||
|
application/x-x509-ca-cert der pem crt;
|
||||||
|
application/x-xpinstall xpi;
|
||||||
|
application/xhtml+xml xhtml;
|
||||||
|
application/xspf+xml xspf;
|
||||||
|
application/zip zip;
|
||||||
|
|
||||||
|
application/octet-stream bin exe dll;
|
||||||
|
application/octet-stream deb;
|
||||||
|
application/octet-stream dmg;
|
||||||
|
application/octet-stream iso img;
|
||||||
|
application/octet-stream msi msp msm;
|
||||||
|
|
||||||
|
audio/midi mid midi kar;
|
||||||
|
audio/mpeg mp3;
|
||||||
|
audio/ogg ogg;
|
||||||
|
audio/x-m4a m4a;
|
||||||
|
audio/x-realaudio ra;
|
||||||
|
|
||||||
|
video/3gpp 3gpp 3gp;
|
||||||
|
video/mp2t ts;
|
||||||
|
video/mp4 mp4;
|
||||||
|
video/mpeg mpeg mpg;
|
||||||
|
video/quicktime mov;
|
||||||
|
video/webm webm;
|
||||||
|
video/x-flv flv;
|
||||||
|
video/x-m4v m4v;
|
||||||
|
video/x-mng mng;
|
||||||
|
video/x-ms-asf asx asf;
|
||||||
|
video/x-ms-wmv wmv;
|
||||||
|
video/x-msvideo avi;
|
||||||
|
}
|
76
util/seed_mime.py
Executable file
76
util/seed_mime.py
Executable file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
def unique(seq):
|
||||||
|
return list(OrderedDict([i, None] for i in seq if i))
|
||||||
|
|
||||||
|
def generate(major, minor, exts):
|
||||||
|
exts = exts[:]
|
||||||
|
yield f"{major}/{minor}", exts
|
||||||
|
if (minor.startswith("x-")):
|
||||||
|
yield f"{major}/{minor[2:]}", exts
|
||||||
|
else:
|
||||||
|
yield f"{major}/x-{minor}", exts
|
||||||
|
for ext in exts:
|
||||||
|
yield f"{major}/{ext}", exts
|
||||||
|
yield f"{major}/x-{ext}", exts
|
||||||
|
|
||||||
|
|
||||||
|
def make(text, final):
|
||||||
|
lines = "".join([
|
||||||
|
line.strip()
|
||||||
|
for line in re.search(r"\{(.*)\}", text, re.S).group(1).split("\n")
|
||||||
|
if line.strip() and not line.strip().startswith("#")
|
||||||
|
]).split(";")
|
||||||
|
|
||||||
|
additional = []
|
||||||
|
for line in lines:
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
m = re.match(r"([a-z1-9]+)/([^\s]+)\s+(.+?)$", line)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
[major, minor, exts] = m.groups()
|
||||||
|
exts = unique(e.lower().strip() for e in exts.split(" ") if e.strip())
|
||||||
|
mime = f"{major}/{minor}"
|
||||||
|
if mime == "application/octet-stream":
|
||||||
|
continue
|
||||||
|
if mime in final:
|
||||||
|
final[mime] += exts
|
||||||
|
continue
|
||||||
|
final[mime] = exts
|
||||||
|
additional += (major, minor, exts),
|
||||||
|
|
||||||
|
for [major, minor, exts] in additional:
|
||||||
|
for [mime, exts] in generate(major, minor, exts):
|
||||||
|
if mime in final:
|
||||||
|
continue
|
||||||
|
final[mime] = exts
|
||||||
|
|
||||||
|
final = OrderedDict()
|
||||||
|
for file in sys.argv[1:]:
|
||||||
|
with open(file, "r") as fp:
|
||||||
|
make(fp.read(), final)
|
||||||
|
|
||||||
|
multi = dict()
|
||||||
|
for [mime, exts] in list(final.items()):
|
||||||
|
exts = unique(exts)
|
||||||
|
prim = exts[0]
|
||||||
|
final[mime] = prim
|
||||||
|
if len(exts) == 1:
|
||||||
|
continue
|
||||||
|
exts = exts[1:]
|
||||||
|
if len(exts) == 1:
|
||||||
|
multi[prim] = exts[0]
|
||||||
|
else:
|
||||||
|
multi[prim] = exts
|
||||||
|
|
||||||
|
final = OrderedDict(sorted(final.items()))
|
||||||
|
multi = OrderedDict(sorted(multi.items()))
|
||||||
|
|
||||||
|
print(json.dumps(dict(e=multi, m=final), indent=2))
|
||||||
|
print("generated", len(final), "mimes", "with", len(multi), "multis", file=sys.stderr)
|
@ -94,8 +94,10 @@ export class TextFilter extends ItemFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allow(item: DownloadItem) {
|
allow(item: DownloadItem) {
|
||||||
return this.expr.test(
|
const {expr} = this;
|
||||||
[item.usable, item.description, item.finalName].join(" "));
|
return expr.test(item.currentName) ||
|
||||||
|
expr.test(item.usable) ||
|
||||||
|
expr.test(item.description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ export class DownloadItem extends EventEmitter {
|
|||||||
|
|
||||||
public error: string;
|
public error: string;
|
||||||
|
|
||||||
public finalName: string;
|
public currentName: string;
|
||||||
|
|
||||||
public ext?: string;
|
public ext?: string;
|
||||||
|
|
||||||
@ -144,6 +144,8 @@ export class DownloadItem extends EventEmitter {
|
|||||||
|
|
||||||
private largeIconField?: string;
|
private largeIconField?: string;
|
||||||
|
|
||||||
|
public opening: boolean;
|
||||||
|
|
||||||
constructor(owner: DownloadTable, raw: any, stats?: Stats) {
|
constructor(owner: DownloadTable, raw: any, stats?: Stats) {
|
||||||
super();
|
super();
|
||||||
Object.assign(this, raw);
|
Object.assign(this, raw);
|
||||||
@ -159,7 +161,7 @@ export class DownloadItem extends EventEmitter {
|
|||||||
return this.iconField;
|
return this.iconField;
|
||||||
}
|
}
|
||||||
this.iconField = this.owner.icons.get(
|
this.iconField = this.owner.icons.get(
|
||||||
iconForPath(this.finalName, ICON_BASE_SIZE));
|
iconForPath(this.currentName, ICON_BASE_SIZE));
|
||||||
if (this.ext) {
|
if (this.ext) {
|
||||||
IconCache.get(this.ext, ICON_REAL_SIZE).then(icon => {
|
IconCache.get(this.ext, ICON_REAL_SIZE).then(icon => {
|
||||||
if (icon) {
|
if (icon) {
|
||||||
@ -178,7 +180,7 @@ export class DownloadItem extends EventEmitter {
|
|||||||
return this.largeIconField;
|
return this.largeIconField;
|
||||||
}
|
}
|
||||||
this.largeIconField = this.owner.icons.get(
|
this.largeIconField = this.owner.icons.get(
|
||||||
iconForPath(this.finalName, LARGE_ICON_BASE_SIZE));
|
iconForPath(this.currentName, LARGE_ICON_BASE_SIZE));
|
||||||
if (this.ext) {
|
if (this.ext) {
|
||||||
IconCache.get(this.ext, LARGE_ICON_REAL_SIZE).then(icon => {
|
IconCache.get(this.ext, LARGE_ICON_REAL_SIZE).then(icon => {
|
||||||
if (icon) {
|
if (icon) {
|
||||||
@ -217,7 +219,7 @@ export class DownloadItem extends EventEmitter {
|
|||||||
if (this.owner.showUrls.value) {
|
if (this.owner.showUrls.value) {
|
||||||
return this.usable;
|
return this.usable;
|
||||||
}
|
}
|
||||||
return this.finalName;
|
return this.currentName;
|
||||||
}
|
}
|
||||||
|
|
||||||
get fmtSize() {
|
get fmtSize() {
|
||||||
@ -607,8 +609,12 @@ export class DownloadTable extends VirtualTable {
|
|||||||
this.selection.clear();
|
this.selection.clear();
|
||||||
|
|
||||||
this.tooltip = null;
|
this.tooltip = null;
|
||||||
this.on("hover", async info => {
|
const tooltipWatcher = new PrefWatcher("tooltip", true);
|
||||||
if (!(await Prefs.get("tooltip"))) {
|
this.on("hover", info => {
|
||||||
|
if (!document.hasFocus()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!tooltipWatcher.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const item = this.downloads.filtered[info.rowid];
|
const item = this.downloads.filtered[info.rowid];
|
||||||
@ -777,7 +783,8 @@ export class DownloadTable extends VirtualTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resumeDownloads(forced = false) {
|
resumeDownloads(forced = false) {
|
||||||
const sids = this.getSelectedSids(DownloadState.RESUMABLE);
|
const sids = this.getSelectedSids(
|
||||||
|
forced ? DownloadState.FORCABLE : DownloadState.RESUMABLE);
|
||||||
if (!sids.length) {
|
if (!sids.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -801,20 +808,30 @@ export class DownloadTable extends VirtualTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async openFile() {
|
async openFile() {
|
||||||
if (this.focusRow < 0) {
|
this.dismissTooltip();
|
||||||
|
const {focusRow} = this;
|
||||||
|
if (focusRow < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const item = this.downloads.filtered[this.focusRow];
|
const item = this.downloads.filtered[focusRow];
|
||||||
if (!item || !item.manId || item.state !== DownloadState.DONE) {
|
if (!item || !item.manId || item.state !== DownloadState.DONE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
item.opening = true;
|
||||||
try {
|
try {
|
||||||
|
this.invalidateRow(focusRow);
|
||||||
await downloads.open(item.manId);
|
await downloads.open(item.manId);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
console.error(ex, ex.toString(), ex);
|
console.error(ex, ex.toString(), ex);
|
||||||
PORT.post("missing", {sid: item.sessionId});
|
PORT.post("missing", {sid: item.sessionId});
|
||||||
}
|
}
|
||||||
|
finally {
|
||||||
|
setTimeout(() => {
|
||||||
|
item.opening = false;
|
||||||
|
this.invalidateRow(focusRow);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async openDirectory() {
|
async openDirectory() {
|
||||||
@ -1111,7 +1128,16 @@ export class DownloadTable extends VirtualTable {
|
|||||||
if (!item) {
|
if (!item) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (item.opening) {
|
||||||
|
return ["opening"];
|
||||||
|
}
|
||||||
const cls = StateClasses.get(item.state);
|
const cls = StateClasses.get(item.state);
|
||||||
|
if (cls && item.opening) {
|
||||||
|
return [cls, "opening"];
|
||||||
|
}
|
||||||
|
if (item.opening) {
|
||||||
|
return ["opening"];
|
||||||
|
}
|
||||||
return cls && [cls] || null;
|
return cls && [cls] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -46,6 +46,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/psl/-/psl-1.1.0.tgz#390c5df1613b166ce3c3eb9fda4d93dc3eeec7b5"
|
resolved "https://registry.yarnpkg.com/@types/psl/-/psl-1.1.0.tgz#390c5df1613b166ce3c3eb9fda4d93dc3eeec7b5"
|
||||||
integrity sha512-HhZnoLAvI2koev3czVPzBNRYvdrzJGLjQbWZhqFmS9Q6a0yumc5qtfSahBGb5g+6qWvA8iiQktqGkwoIXa/BNQ==
|
integrity sha512-HhZnoLAvI2koev3czVPzBNRYvdrzJGLjQbWZhqFmS9Q6a0yumc5qtfSahBGb5g+6qWvA8iiQktqGkwoIXa/BNQ==
|
||||||
|
|
||||||
|
"@types/whatwg-mimetype@^2.1.0":
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f981bbdf1813a75820a6ec3a7fdfa0d452552cc7"
|
||||||
|
integrity sha512-bJ/bZ+pA69lm+Ll8JJRoAD9saH7unIMfxPQQpl7bxa00qNqvUXSyk3xvoRMea1uCpAOxweI7CzjWx48ysX6yug==
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^2.0.0":
|
"@typescript-eslint/eslint-plugin@^2.0.0":
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.0.0.tgz#609a5d7b00ce21a6f94d7ef282eba9da57ca1e42"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.0.0.tgz#609a5d7b00ce21a6f94d7ef282eba9da57ca1e42"
|
||||||
@ -3718,6 +3723,11 @@ webpack@^4.39.3:
|
|||||||
watchpack "^1.6.0"
|
watchpack "^1.6.0"
|
||||||
webpack-sources "^1.4.1"
|
webpack-sources "^1.4.1"
|
||||||
|
|
||||||
|
whatwg-mimetype@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
||||||
|
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
||||||
|
|
||||||
which-module@^2.0.0:
|
which-module@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
|
||||||
|
Reference in New Issue
Block a user