├── .gitignore ├── nativeapp ├── data │ ├── yt2p.png │ ├── mingw32 │ │ ├── lua51.dll │ │ └── luajit.exe │ ├── mingw64 │ │ ├── lua51.dll │ │ └── luajit.exe │ ├── osx32 │ │ ├── luajit-bin │ │ └── luajit │ ├── osx64 │ │ ├── luajit-bin │ │ └── luajit │ ├── linux32 │ │ ├── luajit-bin │ │ └── luajit │ ├── linux64 │ │ ├── luajit-bin │ │ └── luajit │ ├── dialog.lua │ ├── manifestconfig.lua │ ├── luajit.cmd │ ├── luajit │ ├── base64.lua │ ├── uninstall.lua │ ├── install.lua │ ├── dialog_winapi.lua │ ├── json.lua │ ├── host.lua │ ├── dialog_gtk3.lua │ ├── winapi.lua │ └── fs.lua ├── install.bat ├── uninstall.bat ├── install.sh └── uninstall.sh ├── extension ├── icons │ ├── 16 │ │ ├── yt2p.png │ │ └── player.png │ ├── 24 │ │ └── yt2p.png │ ├── 32 │ │ ├── yt2p.png │ │ ├── menus.png │ │ ├── players.png │ │ ├── playerGroups.png │ │ ├── videoLinks.png │ │ ├── embeddedVideos.png │ │ └── fancyImageLinks.png │ ├── 48 │ │ └── yt2p.png │ ├── 64 │ │ ├── yt2p.png │ │ └── warning.png │ └── 96 │ │ └── yt2p.png ├── manifest.json ├── awesomplete.css ├── yt2p-options.css ├── _locales │ ├── en │ │ └── messages.json │ └── et │ │ └── messages.json ├── awesomplete.min.js ├── browser-polyfill.min.js ├── yt2p-background.js ├── yt2p-content.css ├── yt2p-options.html ├── yt2p-options.js └── yt2p-content.js ├── README.md ├── yt2p.sublime-project └── LICENSE.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.xpi 3 | *.zip 4 | *.sublime-workspace 5 | *.eslintrc 6 | *rc.json 7 | -------------------------------------------------------------------------------- /nativeapp/data/yt2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/yt2p.png -------------------------------------------------------------------------------- /nativeapp/install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | cd "%~dp0data" 4 | call luajit "%~n0.lua" %* 5 | -------------------------------------------------------------------------------- /extension/icons/16/yt2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/16/yt2p.png -------------------------------------------------------------------------------- /extension/icons/24/yt2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/24/yt2p.png -------------------------------------------------------------------------------- /extension/icons/32/yt2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/32/yt2p.png -------------------------------------------------------------------------------- /extension/icons/48/yt2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/48/yt2p.png -------------------------------------------------------------------------------- /extension/icons/64/yt2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/64/yt2p.png -------------------------------------------------------------------------------- /extension/icons/96/yt2p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/96/yt2p.png -------------------------------------------------------------------------------- /nativeapp/uninstall.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | cd "%~dp0data" 4 | call luajit "%~n0.lua" %* 5 | -------------------------------------------------------------------------------- /extension/icons/16/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/16/player.png -------------------------------------------------------------------------------- /extension/icons/32/menus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/32/menus.png -------------------------------------------------------------------------------- /extension/icons/32/players.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/32/players.png -------------------------------------------------------------------------------- /extension/icons/64/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/64/warning.png -------------------------------------------------------------------------------- /nativeapp/data/mingw32/lua51.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/mingw32/lua51.dll -------------------------------------------------------------------------------- /nativeapp/data/mingw64/lua51.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/mingw64/lua51.dll -------------------------------------------------------------------------------- /nativeapp/data/osx32/luajit-bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/osx32/luajit-bin -------------------------------------------------------------------------------- /nativeapp/data/osx64/luajit-bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/osx64/luajit-bin -------------------------------------------------------------------------------- /extension/icons/32/playerGroups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/32/playerGroups.png -------------------------------------------------------------------------------- /extension/icons/32/videoLinks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/32/videoLinks.png -------------------------------------------------------------------------------- /nativeapp/data/linux32/luajit-bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/linux32/luajit-bin -------------------------------------------------------------------------------- /nativeapp/data/linux64/luajit-bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/linux64/luajit-bin -------------------------------------------------------------------------------- /nativeapp/data/mingw32/luajit.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/mingw32/luajit.exe -------------------------------------------------------------------------------- /nativeapp/data/mingw64/luajit.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/nativeapp/data/mingw64/luajit.exe -------------------------------------------------------------------------------- /nativeapp/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")/data" 4 | "./luajit" "$(basename "$0" .sh).lua" "$@" 5 | -------------------------------------------------------------------------------- /nativeapp/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd "$(dirname "$0")/data" 4 | "./luajit" "$(basename "$0" .sh).lua" "$@" 5 | -------------------------------------------------------------------------------- /extension/icons/32/embeddedVideos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/32/embeddedVideos.png -------------------------------------------------------------------------------- /extension/icons/32/fancyImageLinks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sumzary/yt2p/HEAD/extension/icons/32/fancyImageLinks.png -------------------------------------------------------------------------------- /nativeapp/data/dialog.lua: -------------------------------------------------------------------------------- 1 | local dialog 2 | 3 | if jit.os == 'Windows' then 4 | dialog = require'dialog_winapi' 5 | elseif jit.os == 'OSX' then 6 | dialog = nil 7 | else 8 | dialog = require'dialog_gtk3' 9 | end 10 | 11 | return dialog 12 | -------------------------------------------------------------------------------- /nativeapp/data/manifestconfig.lua: -------------------------------------------------------------------------------- 1 | return { 2 | name = 'ee.sumzary.yt2p', 3 | neatname = 'YT2Player', 4 | description = 'YouTube 2 Player Native Application', 5 | firefoxaddonid = 'YT2Player@Rasmus.Riiner', 6 | chromeextensionid = 'chrome-extension://NYI/', 7 | } 8 | -------------------------------------------------------------------------------- /nativeapp/data/luajit.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | if [%PROCESSOR_ARCHITECTURE%] == [AMD64] goto arch64 3 | if [%PROCESSOR_ARCHITEW6432%] == [AMD64] goto arch64 4 | 5 | "%~dp0\mingw32\luajit.exe" %* 6 | goto end 7 | 8 | :arch64 9 | "%~dp0\mingw64\luajit.exe" %* 10 | :end 11 | -------------------------------------------------------------------------------- /nativeapp/data/luajit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [ "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64" ] && p=mingw64 || { 3 | [ "$OSTYPE" = "msys" ] && p=mingw32 || { 4 | [ "$(uname -m)" = x86_64 ] && a=64 || a=32 5 | [ "${OSTYPE#darwin}" != "$OSTYPE" ] && p=osx$a || p=linux$a 6 | } 7 | } 8 | n=${0##*/} d=${0%$n} 9 | $d/$p/luajit "$@" 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YouTube 2 Player 2 | A Firefox add-on to let you watch YouTube videos in an external player. 3 | 4 | ### Install Native Application 5 | Run `nativeapp/install.bat` on Windows, `nativeapp/install.sh` on Linux/OSX. 6 | 7 | ### Uninstall Native Application 8 | Run `nativeapp/uninstall.bat` on Windows, `nativeapp/uninstall.sh` on Linux/OSX. 9 | 10 | ### Legacy Version 11 | Check the `legacy` branch. 12 | This works with Firefox <57, Thunderbird, Pale Moon, etc. 13 | 14 | ![](https://addons.cdn.mozilla.net/user-media/previews/full/191/191705.png "A Fancy Image Link") 15 | -------------------------------------------------------------------------------- /nativeapp/data/linux32/luajit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # luajit wrapper that looks for libraries only relative to the current directory 3 | # and relative to the executable's directory, making the distribution portable. 4 | 5 | # get full path of $0 without spawning a new process (this wrapper process is bad enough) 6 | bindir="${0%luajit}" 7 | curdir="$PWD" 8 | cd "$bindir" 9 | bindir="$PWD" 10 | cd "$curdir" 11 | 12 | luadir="$bindir/../.." # platform-indep. Lua modules 13 | platform_dir="$bindir/lua" # platform-specific Lua modules 14 | 15 | export LUA_CPATH="./?.so;$bindir/clib/?.so" 16 | export LUA_PATH="./?.lua;$platform_dir/?.lua;$luadir/?.lua;$luadir/?/init.lua" 17 | export TERRA_PATH="./?.t;$luadir/?.t;$luadir/?/init.t" 18 | exec "$bindir/luajit-bin" "$@" 19 | -------------------------------------------------------------------------------- /nativeapp/data/linux64/luajit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # luajit wrapper that looks for libraries only relative to the current directory 3 | # and relative to the executable's directory, making the distribution portable. 4 | 5 | # get full path of $0 without spawning a new process (this wrapper process is bad enough) 6 | bindir="${0%luajit}" 7 | curdir="$PWD" 8 | cd "$bindir" 9 | bindir="$PWD" 10 | cd "$curdir" 11 | 12 | luadir="$bindir/../.." # platform-indep. Lua modules 13 | platform_dir="$bindir/lua" # platform-specific Lua modules 14 | 15 | export LUA_CPATH="./?.so;$bindir/clib/?.so" 16 | export LUA_PATH="./?.lua;$platform_dir/?.lua;$luadir/?.lua;$luadir/?/init.lua" 17 | export TERRA_PATH="./?.t;$luadir/?.t;$luadir/?/init.t" 18 | exec "$bindir/luajit-bin" "$@" 19 | -------------------------------------------------------------------------------- /nativeapp/data/osx32/luajit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # luajit wrapper that looks for libraries only relative to the current directory 3 | # and relative to the executable's directory, making the distribution portable. 4 | 5 | # get full path of $0 without spawning a new process (this wrapper process is bad enough) 6 | bindir="${0%luajit}" 7 | curdir="$PWD" 8 | cd "$bindir" 9 | bindir="$PWD" 10 | cd "$curdir" 11 | 12 | luadir="$bindir/../.." # platform-indep. Lua modules 13 | platform_dir="$bindir/lua" # platform-specific Lua modules 14 | 15 | export LUA_CPATH="./?.so;$bindir/clib/?.so" 16 | export LUA_PATH="./?.lua;$platform_dir/?.lua;$luadir/?.lua;$luadir/?/init.lua" 17 | export TERRA_PATH="./?.t;$luadir/?.t;$luadir/?/init.t" 18 | exec "$bindir/luajit-bin" "$@" 19 | -------------------------------------------------------------------------------- /nativeapp/data/osx64/luajit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # luajit wrapper that looks for libraries only relative to the current directory 3 | # and relative to the executable's directory, making the distribution portable. 4 | 5 | # get full path of $0 without spawning a new process (this wrapper process is bad enough) 6 | bindir="${0%luajit}" 7 | curdir="$PWD" 8 | cd "$bindir" 9 | bindir="$PWD" 10 | cd "$curdir" 11 | 12 | luadir="$bindir/../.." # platform-indep. Lua modules 13 | platform_dir="$bindir/lua" # platform-specific Lua modules 14 | 15 | export LUA_CPATH="./?.so;$bindir/clib/?.so" 16 | export LUA_PATH="./?.lua;$platform_dir/?.lua;$luadir/?.lua;$luadir/?/init.lua" 17 | export TERRA_PATH="./?.t;$luadir/?.t;$luadir/?/init.t" 18 | exec "$bindir/luajit-bin" "$@" 19 | -------------------------------------------------------------------------------- /yt2p.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": 3 | [ 4 | { 5 | "name": "YT2P", 6 | "shell_cmd": "./build.sh", 7 | "working_dir": "$folder", 8 | "variants": 9 | [ 10 | { 11 | "name": "Zip Extension", 12 | "shell_cmd": "zip -r ../build/yt2p-extension.xpi .", 13 | "working_dir": "$folder/extension" 14 | }, 15 | { 16 | "name": "Run", 17 | "shell_cmd": "web-ext run --bc -p default", 18 | "working_dir": "$folder/extension" 19 | }, 20 | { 21 | "name": "Install", 22 | "shell_cmd": "./install.sh -y", 23 | "working_dir": "$folder/nativeapp" 24 | }, 25 | { 26 | "name": "Uninstall", 27 | "shell_cmd": "./uninstall.sh -y", 28 | "working_dir": "$folder/nativeapp" 29 | } 30 | ] 31 | } 32 | ], 33 | "folders": 34 | [ 35 | { 36 | "path": "." 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2016-2018 Rasmus Riiner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /nativeapp/data/base64.lua: -------------------------------------------------------------------------------- 1 | local base64 = {} 2 | 3 | local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' 4 | 5 | function base64.encode(data) 6 | return ((data:gsub('.', function(x) 7 | local r,b='',x:byte() 8 | for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end 9 | return r 10 | end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) 11 | if (#x < 6) then return '' end 12 | local c=0 13 | for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end 14 | return b:sub(c+1,c+1) 15 | end)..({ '', '==', '=' })[#data%3+1]) 16 | end 17 | 18 | function base64.decode(data) 19 | data = string.gsub(data, '[^'..b..'=]', '') 20 | return (data:gsub('.', function(x) 21 | if (x == '=') then return '' end 22 | local r,f='',(b:find(x)-1) 23 | for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end 24 | return r 25 | end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) 26 | if (#x ~= 8) then return '' end 27 | local c=0 28 | for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end 29 | return string.char(c) 30 | end)) 31 | end 32 | 33 | return base64 34 | -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "default_locale": "en", 4 | "version": "4.4", 5 | "name": "__MSG_extensionName__", 6 | "description": "__MSG_extensionDescription__", 7 | "permissions": [ 8 | "", 9 | "notifications", 10 | "contextMenus", 11 | "storage", 12 | "nativeMessaging", 13 | "downloads" 14 | ], 15 | "web_accessible_resources": [ 16 | "icons/*" 17 | ], 18 | "applications": { 19 | "gecko": { 20 | "id": "YT2Player@Rasmus.Riiner", 21 | "strict_min_version": "52.0a1" 22 | } 23 | }, 24 | "icons": { 25 | "16": "icons/16/yt2p.png", 26 | "24": "icons/24/yt2p.png", 27 | "32": "icons/32/yt2p.png", 28 | "48": "icons/48/yt2p.png", 29 | "64": "icons/64/yt2p.png", 30 | "96": "icons/96/yt2p.png" 31 | }, 32 | "background": { 33 | "scripts": [ 34 | "browser-polyfill.min.js", 35 | "yt2p-background.js" 36 | ] 37 | }, 38 | "content_scripts": [ 39 | { 40 | "matches": [ 41 | "" 42 | ], 43 | "js": [ 44 | "browser-polyfill.min.js", 45 | "yt2p-content.js" 46 | ], 47 | "css": [ 48 | "yt2p-content.css" 49 | ], 50 | "run_at": "document_end", 51 | "all_frames": true 52 | } 53 | ], 54 | "options_ui": { 55 | "browser_style": true, 56 | "page": "yt2p-options.html" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /extension/awesomplete.css: -------------------------------------------------------------------------------- 1 | .awesomplete [hidden] { 2 | display: none; 3 | } 4 | 5 | .awesomplete .visually-hidden { 6 | position: absolute; 7 | clip: rect(0, 0, 0, 0); 8 | } 9 | 10 | .awesomplete { 11 | display: inline-block; 12 | position: relative; 13 | } 14 | 15 | .awesomplete > input { 16 | display: block; 17 | } 18 | 19 | .awesomplete > ul { 20 | position: absolute; 21 | left: 0; 22 | z-index: 1; 23 | min-width: 100%; 24 | box-sizing: border-box; 25 | list-style: none; 26 | padding: 0; 27 | margin: 0; 28 | background: #fff; 29 | } 30 | 31 | .awesomplete > ul:empty { 32 | display: none; 33 | } 34 | 35 | .awesomplete > ul { 36 | border-radius: .3em; 37 | margin: .2em 0 0; 38 | background: hsla(0,0%,100%,.9); 39 | background: linear-gradient(to bottom right, white, hsla(0,0%,100%,.8)); 40 | border: 1px solid rgba(0,0,0,.3); 41 | box-shadow: .05em .2em .6em rgba(0,0,0,.2); 42 | text-shadow: none; 43 | } 44 | 45 | @supports (transform: scale(0)) { 46 | .awesomplete > ul { 47 | transition: .3s cubic-bezier(.4,.2,.5,1.4); 48 | transform-origin: 1.43em -.43em; 49 | } 50 | 51 | .awesomplete > ul[hidden], 52 | .awesomplete > ul:empty { 53 | opacity: 0; 54 | transform: scale(0); 55 | display: block; 56 | transition-timing-function: ease; 57 | } 58 | } 59 | 60 | /* Pointer */ 61 | .awesomplete > ul:before { 62 | content: ""; 63 | position: absolute; 64 | top: -.43em; 65 | left: 1em; 66 | width: 0; height: 0; 67 | padding: .4em; 68 | background: white; 69 | border: inherit; 70 | border-right: 0; 71 | border-bottom: 0; 72 | -webkit-transform: rotate(45deg); 73 | transform: rotate(45deg); 74 | } 75 | 76 | .awesomplete > ul > li { 77 | position: relative; 78 | padding: .2em .5em; 79 | cursor: pointer; 80 | } 81 | 82 | .awesomplete > ul > li:hover { 83 | background: hsl(200, 40%, 80%); 84 | color: black; 85 | } 86 | 87 | .awesomplete > ul > li[aria-selected="true"] { 88 | background: hsl(205, 40%, 40%); 89 | color: white; 90 | } 91 | 92 | .awesomplete mark { 93 | background: hsl(65, 100%, 50%); 94 | } 95 | 96 | .awesomplete li:hover mark { 97 | background: hsl(68, 100%, 41%); 98 | } 99 | 100 | .awesomplete li[aria-selected="true"] mark { 101 | background: hsl(86, 100%, 21%); 102 | color: inherit; 103 | } 104 | /*# sourceMappingURL=awesomplete.css.map */ 105 | -------------------------------------------------------------------------------- /nativeapp/data/uninstall.lua: -------------------------------------------------------------------------------- 1 | local cfg = require'manifestconfig' 2 | local WIN = jit.os == 'Windows' 3 | local OSX = jit.os == 'OSX' 4 | local SEP = WIN and '\\' or '/' 5 | local format, gsub = string.format, string.gsub 6 | 7 | local function capture(cmd) 8 | local f = io.popen(cmd) 9 | local s = f:read'*a' 10 | f:close() 11 | return gsub(gsub(gsub(s, '^%s+', ''), '%s+$', ''), '[\n\r]+', ' ') 12 | end 13 | 14 | local function rmdir(dir) 15 | os.execute(format(WIN and 'rmdir /q /s %q' or 'rm -rf %q', dir)) 16 | end 17 | 18 | local function unclient(dir) 19 | rmdir(dir) 20 | print(format('- %s%s', dir, SEP)) 21 | end 22 | 23 | if WIN then 24 | local function unreg(reg) 25 | os.execute(format('reg delete "%s\\%s" /f', reg, cfg.name)) 26 | print(format('- %s', reg)) 27 | end 28 | unreg('HKCU\\Software\\Mozilla\\NativeMessagingHosts') 29 | unreg('HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts') 30 | unclient(format('%s\\%s', os.getenv'ProgramFiles', cfg.neatname)) 31 | else 32 | local function unnmh(dir) 33 | local path = format('%s/%s.json', dir, cfg.name) 34 | os.execute(format('rm -f %q', path)) 35 | print(format('- %s', path)) 36 | end 37 | local root = capture'id -un' == 'root' 38 | local home = os.getenv'HOME' 39 | if OSX then 40 | if root then 41 | unnmh('/Library/Mozilla/NativeMessagingHosts') 42 | unnmh('/Library/Vivaldi/NativeMessagingHosts') 43 | unnmh('/Library/Chromium/NativeMessagingHosts') 44 | unnmh('/Library/Google/Chrome/NativeMessagingHosts') 45 | else 46 | unnmh(home..'/Library/Application Support/Mozilla/NativeMessagingHosts') 47 | unnmh(home..'/Library/Application Support/Vivaldi/NativeMessagingHosts') 48 | unnmh(home..'/Library/Application Support/Chromium/NativeMessagingHosts') 49 | unnmh(home..'/Library/Application Support/Google/Chrome/NativeMessagingHosts') 50 | end 51 | unclient(format('%s/Library/Application Support/Mozilla/%s', home, cfg.neatname)) 52 | else 53 | if root then 54 | unnmh('/usr/lib/mozilla/native-messaging-hosts') 55 | unnmh('/etc/vivaldi/native-messaging-hosts') 56 | unnmh('/etc/chromium/native-messaging-hosts') 57 | unnmh('/etc/opt/chrome/native-messaging-hosts') 58 | else 59 | unnmh(home..'/.mozilla/native-messaging-hosts') 60 | unnmh(home..'/.config/vivaldi/NativeMessagingHosts') 61 | unnmh(home..'/.config/chromium/NativeMessagingHosts') 62 | unnmh(home..'/.config/google-chrome/NativeMessagingHosts') 63 | end 64 | unclient(format('%s/.local/lib/%s', home, cfg.name)) 65 | end 66 | end 67 | 68 | if ... ~= '-y' then 69 | io.write'Done! Press enter to close...' 70 | io.read() 71 | end 72 | -------------------------------------------------------------------------------- /extension/yt2p-options.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0 auto !important; 3 | padding: 2em; 4 | height: 100%; 5 | width: 100%; 6 | min-width: 35em; 7 | max-width: 70em; 8 | box-sizing: border-box; 9 | font-family: Ubuntu, Arial, Helvetica, sans-serif; 10 | /*overflow: hidden;*/ 11 | } 12 | .vbox { 13 | margin: 3px; 14 | display: flex; 15 | flex-direction: column; 16 | align-content: flex-start; 17 | } 18 | .hbox { 19 | margin: 3px; 20 | display: flex; 21 | flex-direction: row; 22 | align-content: flex-start; 23 | } 24 | .nomargin { 25 | margin: 0; 26 | } 27 | .flex { 28 | flex: 1; 29 | } 30 | .flex2 { 31 | flex: 2; 32 | } 33 | .flex3 { 34 | flex: 3; 35 | } 36 | .flex4 { 37 | flex: 4; 38 | } 39 | .stretch { 40 | align-self: stretch; 41 | } 42 | .main-start { 43 | justify-content: flex-start; 44 | } 45 | .cross-start { 46 | align-items: flex-start; 47 | align-content: flex-start; 48 | } 49 | .main-center { 50 | justify-content: center; 51 | } 52 | .cross-center { 53 | align-items: center; 54 | align-content: center; 55 | } 56 | .main-end { 57 | justify-content: flex-end; 58 | } 59 | .cross-end { 60 | align-items: flex-end; 61 | align-content: flex-end; 62 | } 63 | .cross-stretch { 64 | align-items: stretch; 65 | align-content: stretch; 66 | } 67 | .wrap { 68 | flex-wrap: wrap; 69 | } 70 | .none { 71 | display: none; 72 | } 73 | 74 | input[type="text"] { 75 | min-width: 2em; 76 | } 77 | 78 | #optionbuttons { 79 | margin-top: 10px; 80 | margin-bottom: 20px; 81 | } 82 | #export, #import, #defaults { 83 | margin-right: .5em; 84 | font-size: 1.2em; 85 | } 86 | 87 | #groups, #players { 88 | width: 100%; 89 | } 90 | .option { 91 | height: 18px; 92 | align-content: center; 93 | padding-left: 20px; 94 | } 95 | #image { 96 | width: 16px; 97 | height: 16px; 98 | margin: 4px; 99 | } 100 | a.button { 101 | color: black; 102 | font-size: 2em; 103 | text-decoration: none; 104 | cursor: pointer; 105 | } 106 | 107 | #cut, #copy, #paste, #command, #clipboard, #clickCommand { 108 | font-family: Ubuntu Mono, Lucida Console, Courier, monospace; 109 | cursor: help; 110 | } 111 | 112 | .header ~ * { 113 | margin-left: 36px; 114 | } 115 | 116 | .icon32 { 117 | width: 32px; 118 | height: 32px; 119 | margin-right: 4px; 120 | } 121 | .icon64 { 122 | width: 64px; 123 | height: 64px; 124 | margin: 18px; 125 | } 126 | 127 | #icmis, #icmp { 128 | width: 5em; 129 | } 130 | .left-label { 131 | margin-right: 4px; 132 | } 133 | .right-label { 134 | margin-left: 4px; 135 | } 136 | 137 | .awesomplete { 138 | flex: 1; 139 | } 140 | .awesomplete input { 141 | width: 100%; 142 | box-sizing: border-box; 143 | } 144 | 145 | .subprefs { 146 | margin-top: 0; 147 | margin-left: 70px; 148 | } 149 | 150 | #nativeMissing, #nativeUpgrade { 151 | margin-bottom: 16px; 152 | border-radius: 8px; 153 | } 154 | #nativeMissing { 155 | background-color: orange; 156 | } 157 | #nativeUpgrade { 158 | background-color: lightblue; 159 | } 160 | 161 | .title-hint label { 162 | border-bottom: 1px dotted black; 163 | cursor: help; 164 | } 165 | -------------------------------------------------------------------------------- /extension/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionName": { "message": "YouTube 2 Player" }, 3 | "extensionDescription": { "message": "Watch videos in an external player." }, 4 | "import": { "message": "Import" }, 5 | "export": { "message": "Export" }, 6 | "defaults": { "message": "Defaults" }, 7 | "toolsMenuItem": { "message": "YT2Mängijad" }, 8 | "mainGroup": { "message": "Main players" }, 9 | "importedGroup": { "message": "(imported players)" }, 10 | "sendToPlayer": { "message": "Send to player" }, 11 | "options": { "message": "YT2Player Options" }, 12 | "groups": { "message": "Groups" }, 13 | "players": { "message": "Players" }, 14 | "unnamedOption": { "message": "" }, 15 | "new": { "message": "New" }, 16 | "newSeparator": { "message": "New Separator" }, 17 | "delete": { "message": "Delete" }, 18 | "cut": { "message": "Cut" }, 19 | "copy": { "message": "Copy" }, 20 | "paste": { "message": "Paste" }, 21 | "moveUp": { "message": "Move Up" }, 22 | "moveDown": { "message": "Move Down" }, 23 | "browse": { "message": "Browse" }, 24 | "selectIcon": { "message": "Select icon" }, 25 | "selectApplication": { "message": "Select application" }, 26 | "extras": { "message": "Extras" }, 27 | "name": { "message": "Name" }, 28 | "icon": { "message": "Icon" }, 29 | "command": { "message": "Command (hover for more info)" }, 30 | "commandTooltip": { "message": "Filename with spaces must be in quotes!\n\nDon't forget to add one of the following:\n\nLINKURL: Raw link URL\nVIDEOID: Video ID\nVIDEOURL: Standardized video URL\nVIDEOURLWITHPLAYLIST: VIDEOURL with playlist ID" }, 31 | "clipboard": { "message": "Clipboard" }, 32 | "menus": { "message": "Menus" }, 33 | "iconContextMenuEnabled": { "message": "Icon Context Menu" }, 34 | "iconContextMenuEnabledTooltip": { "message": "Right-click video link to open the Icon Context Menu.\n\nDouble right-click for the regular browser context menu." }, 35 | "contextMenuItemsEnabled": { "message": "Context menu items" }, 36 | "theme": { "message": "theme" }, 37 | "default": { "message": "Default" }, 38 | "light": { "message": "Light" }, 39 | "dark": { "message": "Dark" }, 40 | "iconSize": { "message": "pixels in size" }, 41 | "padding": { "message": "pixels of padding" }, 42 | "custom": { "message": "Custom" }, 43 | "videoLinks": { "message": "Video Links" }, 44 | "videoLinkChangeEnabled": { "message": "Change video links" }, 45 | "videoLinkChangeUnderline": { "message": "with a red underline" }, 46 | "videoLinkChangeGlow": { "message": "with a red glow" }, 47 | "videoLinkChangeEmbed": { "message": "into embedded videos" }, 48 | "videoLinkChangeFil": { "message": "into Fancy Image Links" }, 49 | "videoLinkClickChangeEnabled": { "message": "Change video link click" }, 50 | "videoLinkClickChangeMenu": { "message": "to open the Icon Context Menu" }, 51 | "videoLinkClickChangeEmbed": { "message": "to turn it into an embedded video" }, 52 | "videoLinkClickChangeFil": { "message": "to turn it into a Fancy Image Link" }, 53 | "embeddedVideoChangeEnabled": { "message": "Change embedded videos" }, 54 | "embeddedVideoChangeLink": { "message": "into text links" }, 55 | "embeddedVideoChangeEmbed": { "message": "into <object> elements" }, 56 | "embeddedVideoChangeFil": { "message": "into Fancy Image Links" }, 57 | "filClickChangeEnabled": { "message": "Change Fancy Image Link click" }, 58 | "filClickChangeLink": { "message": "turn it into a text link" }, 59 | "filClickChangeEmbed": { "message": "turn it into an embedded video" }, 60 | "embeddedVideos": { "message": "Embedded Videos" }, 61 | "fancyImageLinks": { "message": "Fancy Image Links" }, 62 | "videoNotFound": { "message": "Video not found" }, 63 | "commandNotSet": { "message": "Player command not set.\nCheck the options!" }, 64 | "nativeAppMissing": { "message": "Native app not installed.\nCheck the options!" } 65 | } 66 | -------------------------------------------------------------------------------- /extension/_locales/et/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionName": { "message": "YouTube 2 Mängija" }, 3 | "extensionDescription": { "message": "Vaata videoklippe välises mängijas." }, 4 | "import": { "message": "Impordi" }, 5 | "export": { "message": "Ekspordi" }, 6 | "defaults": { "message": "Taasta vaikimisi" }, 7 | "toolsMenuItem": { "message": "YT2Players" }, 8 | "mainGroup": { "message": "Peamised mängijad" }, 9 | "importedGroup": { "message": "(imporditud mängijad)" }, 10 | "sendToPlayer": { "message": "Saada mängijasse" }, 11 | "options": { "message": "YT2P Sätted" }, 12 | "groups": { "message": "Rühmad" }, 13 | "players": { "message": "Mängijad" }, 14 | "unnamedOption": { "message": "" }, 15 | "new": { "message": "Uus" }, 16 | "newSeparator": { "message": "Uus Eraldaja" }, 17 | "delete": { "message": "Kustuta" }, 18 | "cut": { "message": "Lõika" }, 19 | "copy": { "message": "Kopeeri" }, 20 | "paste": { "message": "Aseta" }, 21 | "moveUp": { "message": "Liiguta Üles" }, 22 | "moveDown": { "message": "Liiguta Alla" }, 23 | "browse": { "message": "Sirvi" }, 24 | "selectIcon": { "message": "Vali ikoon" }, 25 | "selectApplication": { "message": "Vali programm" }, 26 | "extras": { "message": "Lisad" }, 27 | "name": { "message": "Nimi" }, 28 | "icon": { "message": "Ikoon" }, 29 | "command": { "message": "Käsk (veel infot hiirt hõljutades)" }, 30 | "commandTooltip": { "message": "Tühikutega failinimi peab olema jutumärkides!\n\nÄra unusta lisada vähemalt ühte järgmistest:\n\nLINKURL: Toores lingi URL\nVIDEOID: Klipi ID\nVIDEOURL: Standardiseeritud klipi URL\nVIDEOURLWITHPLAYLIST: VIDEOURL esitusloendi ID-ga" }, 31 | "clipboard": { "message": "Kopeerimine" }, 32 | "menus": { "message": "Menüüd" }, 33 | "iconContextMenuEnabled": { "message": "Ikoon-kontekstmenüü" }, 34 | "iconContextMenuEnabledTooltip": { "message": "Kontekst-vajuta klipi lingile Ikoon-kontekstmenüü avamiseks.\n\nKaksik-kontekst-vajuta tava-kontekstmenüü avamiseks." }, 35 | "contextMenuItemsEnabled": { "message": "Tava-kontekstmenüü nupud" }, 36 | "theme": { "message": "teema" }, 37 | "default": { "message": "Tava" }, 38 | "light": { "message": "Hele" }, 39 | "dark": { "message": "Tume" }, 40 | "iconSize": { "message": "pikslit suur" }, 41 | "padding": { "message": "pikslit polstrit" }, 42 | "custom": { "message": "Käsitsi" }, 43 | "videoLinks": { "message": "Klipilingid" }, 44 | "videoLinkChangeEnabled": { "message": "Muuda klippide linke" }, 45 | "videoLinkChangeUnderline": { "message": "punase alljooonega" }, 46 | "videoLinkChangeGlow": { "message": "punase helgiga" }, 47 | "videoLinkChangeEmbed": { "message": "manustatud klippideks" }, 48 | "videoLinkChangeFil": { "message": "Piltlinkideks" }, 49 | "videoLinkClickChangeEnabled": { "message": "Klipilingi vajutamine" }, 50 | "videoLinkClickChangeMenu": { "message": "avab Ikoon-kontekstmenüü" }, 51 | "videoLinkClickChangeEmbed": { "message": "teeb sellest manustatud klipi" }, 52 | "videoLinkClickChangeFil": { "message": "teeb sellest Piltlingi" }, 53 | "embeddedVideoChangeEnabled": { "message": "Muuda manustatud klipid" }, 54 | "embeddedVideoChangeLink": { "message": "tekstilinkideks" }, 55 | "embeddedVideoChangeEmbed": { "message": "<object> elementideks" }, 56 | "embeddedVideoChangeFil": { "message": "Piltlinkideks" }, 57 | "filClickChangeEnabled": { "message": "Piltlingi vajutamine" }, 58 | "filClickChangeSend": { "message": "saadab mängijasse" }, 59 | "filClickChangeLink": { "message": "teeb sellest tekstilingi" }, 60 | "filClickChangeEmbed": { "message": "teeb sellest manustatud klipi" }, 61 | "embeddedVideos": { "message": "Manustatud Klipid" }, 62 | "fancyImageLinks": { "message": "Piltlingid" }, 63 | "videoNotFound": { "message": "Klippi ei leitud" }, 64 | "commandNotSet": { "message": "Mängija käsk pole seatud.\nKontrolli sätteid!" }, 65 | "nativeAppMissing": { "message": "Kohalik programm on paigaldamata.\nKontrolli sätteid!" } 66 | } 67 | -------------------------------------------------------------------------------- /nativeapp/data/install.lua: -------------------------------------------------------------------------------- 1 | local cfg = require'manifestconfig' 2 | local WIN = jit.os == 'Windows' 3 | local OSX = jit.os == 'OSX' 4 | local SEP = WIN and '\\' or '/' 5 | local format, gsub = string.format, string.gsub 6 | 7 | local function capture(cmd) 8 | local f = io.popen(cmd) 9 | local s = f:read'*a' 10 | f:close() 11 | return gsub(gsub(gsub(s, '^%s+', ''), '%s+$', ''), '[\n\r]+', ' ') 12 | end 13 | 14 | local function mkdir(dir) 15 | os.execute(format(WIN and 'mkdir %q' or 'mkdir -p %q', dir)) 16 | end 17 | 18 | local function cpdir(dir, dest) 19 | os.execute(format(WIN and 'xcopy /s /e /y %q %q' or 'cp -R %q %q', dir, dest)) 20 | end 21 | 22 | local NMHJSON = [[{ 23 | "name": %q, 24 | "description": %q, 25 | "path": %q, 26 | "type": "stdio", 27 | %q: [%q] 28 | } 29 | ]] 30 | local function donmh(dir, ncdir, browser) 31 | mkdir(dir) 32 | local json = format(NMHJSON, 33 | cfg.name, cfg.description, 34 | format('%s%s%s', ncdir, SEP, WIN and 'host.bat' or 'host.sh'), 35 | (browser == 'firefox') and 'allowed_extensions' or 'allowed_origins', 36 | (browser == 'firefox') and cfg.firefoxaddonid or cfg.chromeextensionid) 37 | local path = format('%s%s%s%s.json', dir, SEP, cfg.name, WIN and format('_%s', browser) or '') 38 | local fp = assert(io.open(path, 'w+')) 39 | assert(fp:write(json)) 40 | fp:close() 41 | if not WIN then 42 | os.execute(format('chmod o+r %q', path)) 43 | end 44 | print(format('+ %s', path)) 45 | return path 46 | end 47 | 48 | local LAUNCHER_BATCH = [[ 49 | @echo off 50 | setlocal 51 | cd "%~dp0data" 52 | call luajit "%~n0.lua" %* 53 | ]] 54 | local LAUNCHER_SHELL = [[ 55 | #!/bin/sh 56 | 57 | cd "$(dirname "$0")/data" 58 | "./luajit" "$(basename "$0" .sh).lua" "$@" 59 | ]] 60 | local function dolaunchers(name, dir) 61 | dir = dir or '..' 62 | local path = dir..SEP..name 63 | local f 64 | f = io.open(path..'.bat', 'w+b') 65 | f:write(LAUNCHER_BATCH) 66 | f:close() 67 | f = io.open(path..'.sh', 'w+b') 68 | f:write(LAUNCHER_SHELL) 69 | f:close() 70 | if not WIN then 71 | os.execute(format('chmod +x %q', path..'.sh')) 72 | end 73 | end 74 | 75 | local function doclient(dir) 76 | local datadir = dir..SEP..'data' 77 | mkdir(dir) 78 | mkdir(datadir) 79 | cpdir('.', datadir) 80 | dolaunchers('host', dir) 81 | dolaunchers('uninstall', dir) 82 | print(format('+ %s%s', dir, SEP)) 83 | print(format('+ %s%s', datadir, SEP)) 84 | end 85 | 86 | if WIN then 87 | local function doreg(reg, dir) 88 | os.execute(format('reg add "%s" /ve /t REG_SZ /d "%s" /f', reg, dir)) 89 | print(format('+ %s', reg)) 90 | end 91 | local dir = format('%s\\%s', os.getenv'ProgramFiles', cfg.neatname) 92 | doclient(dir) 93 | local ffpath = donmh(dir, dir, 'firefox') 94 | local gcpath = donmh(dir, dir, 'chrome') 95 | doreg(format('HKCU\\Software\\Mozilla\\NativeMessagingHosts\\%s', cfg.name), ffpath) 96 | doreg(format('HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\%s', cfg.name), gcpath) 97 | else 98 | local root = capture'id -un' == 'root' 99 | local home = os.getenv'HOME' 100 | if OSX then 101 | local dir = format('%s/Library/Application Support/Mozilla/%s', home, cfg.neatname) 102 | doclient(dir) 103 | if root then 104 | donmh('/Library/Mozilla/NativeMessagingHosts', dir, 'firefox') 105 | donmh('/Library/Vivaldi/NativeMessagingHosts', dir) 106 | donmh('/Library/Chromium/NativeMessagingHosts', dir) 107 | donmh('/Library/Google/Chrome/NativeMessagingHosts', dir) 108 | else 109 | donmh(home..'/Library/Application Support/Mozilla/NativeMessagingHosts', dir, 'firefox') 110 | donmh(home..'/Library/Application Support/Vivaldi/NativeMessagingHosts', dir) 111 | donmh(home..'/Library/Application Support/Chromium/NativeMessagingHosts', dir) 112 | donmh(home..'/Library/Application Support/Google/Chrome/NativeMessagingHosts', dir) 113 | end 114 | else 115 | local dir = format('%s/.local/lib/%s', home, cfg.name) 116 | doclient(dir) 117 | if root then 118 | donmh('/usr/lib/mozilla/native-messaging-hosts', dir, 'firefox') 119 | donmh('/etc/vivaldi/native-messaging-hosts', dir) 120 | donmh('/etc/chromium/native-messaging-hosts', dir) 121 | donmh('/etc/opt/chrome/native-messaging-hosts', dir) 122 | else 123 | donmh(home..'/.mozilla/native-messaging-hosts', dir, 'firefox') 124 | donmh(home..'/.config/vivaldi/NativeMessagingHosts', dir) 125 | donmh(home..'/.config/chromium/NativeMessagingHosts', dir) 126 | donmh(home..'/.config/google-chrome/NativeMessagingHosts', dir) 127 | end 128 | end 129 | end 130 | 131 | dolaunchers('uninstall') 132 | 133 | if ... ~= '-y' then 134 | io.write'Done! Press enter to close...' 135 | io.read() 136 | end 137 | -------------------------------------------------------------------------------- /nativeapp/data/dialog_winapi.lua: -------------------------------------------------------------------------------- 1 | local dialog = {} 2 | 3 | local ffi = require'ffi' 4 | local fs = require'fs' 5 | local winapi = require'winapi' 6 | 7 | ffi.cdef[[ 8 | 9 | // wine/include/ 10 | 11 | // commdlg.h 12 | 13 | typedef UINT_PTR (*LPOFNHOOKPROC) (HWND, UINT, WPARAM, LPARAM); 14 | typedef struct tagOFNW { 15 | DWORD lStructSize; 16 | HWND hwndOwner; 17 | HINSTANCE hInstance; 18 | LPCWSTR lpstrFilter; 19 | LPWSTR lpstrCustomFilter; 20 | DWORD nMaxCustFilter; 21 | DWORD nFilterIndex; 22 | LPWSTR lpstrFile; 23 | DWORD nMaxFile; 24 | LPWSTR lpstrFileTitle; 25 | DWORD nMaxFileTitle; 26 | LPCWSTR lpstrInitialDir; 27 | LPCWSTR lpstrTitle; 28 | DWORD Flags; 29 | WORD nFileOffset; 30 | WORD nFileExtension; 31 | LPCWSTR lpstrDefExt; 32 | LPARAM lCustData; 33 | LPOFNHOOKPROC lpfnHook; 34 | LPCWSTR lpTemplateName; 35 | void* pvReserved; 36 | DWORD dwReserved; 37 | DWORD FlagsEx; 38 | } OPENFILENAMEW, *LPOPENFILENAMEW; 39 | DWORD CommDlgExtendedError(); 40 | BOOL GetOpenFileNameW(LPOPENFILENAMEW); 41 | BOOL GetSaveFileNameW(LPOPENFILENAMEW); 42 | ]] 43 | 44 | local CD = ffi.load'comdlg32' 45 | 46 | local commdlgerrnames = { 47 | [0xFFFF] = 'CDERR_DIALOGFAILURE', 48 | [0x0000] = 'CDERR_GENERALCODES', 49 | [0x0001] = 'CDERR_STRUCTSIZE', 50 | [0x0002] = 'CDERR_INITIALIZATION', 51 | [0x0003] = 'CDERR_NOTEMPLATE', 52 | [0x0004] = 'CDERR_NOHINSTANCE', 53 | [0x0005] = 'CDERR_LOADSTRFAILURE', 54 | [0x0006] = 'CDERR_FINDRESFAILURE', 55 | [0x0007] = 'CDERR_LOADRESFAILURE', 56 | [0x0008] = 'CDERR_LOCKRESFAILURE', 57 | [0x0009] = 'CDERR_MEMALLOCFAILURE', 58 | [0x000A] = 'CDERR_MEMLOCKFAILURE', 59 | [0x000B] = 'CDERR_NOHOOK', 60 | [0x000C] = 'CDERR_REGISTERMSGFAIL', 61 | [0x1000] = 'PDERR_PRINTERCODES', 62 | [0x1001] = 'PDERR_SETUPFAILURE', 63 | [0x1002] = 'PDERR_PARSEFAILURE', 64 | [0x1003] = 'PDERR_RETDEFFAILURE', 65 | [0x1004] = 'PDERR_LOADDRVFAILURE', 66 | [0x1005] = 'PDERR_GETDEVMODEFAIL', 67 | [0x1006] = 'PDERR_INITFAILURE', 68 | [0x1007] = 'PDERR_NODEVICES', 69 | [0x1008] = 'PDERR_NODEFAULTPRN', 70 | [0x1009] = 'PDERR_DNDMMISMATCH', 71 | [0x100A] = 'PDERR_CREATEICFAILURE', 72 | [0x100B] = 'PDERR_PRINTERNOTFOUND', 73 | [0x100C] = 'PDERR_DEFAULTDIFFERENT', 74 | [0x2000] = 'CFERR_CHOOSEFONTCODES', 75 | [0x2001] = 'CFERR_NOFONTS', 76 | [0x2002] = 'CFERR_MAXLESSTHANMIN', 77 | [0x3000] = 'FNERR_FILENAMECODES', 78 | [0x3001] = 'FNERR_SUBCLASSFAILURE', 79 | [0x3002] = 'FNERR_INVALIDFILENAME', 80 | [0x3003] = 'FNERR_BUFFERTOOSMALL', 81 | [0x4000] = 'FRERR_FINDREPLACECODES', 82 | [0x4001] = 'FRERR_BUFFERLENGTHZERO', 83 | [0x5000] = 'CCERR_CHOOSECOLORCODES', 84 | } 85 | local function chkcomdlg(ret) 86 | if ret == 0 then 87 | local err = CD.CommDlgExtendedError() 88 | assert(err == 0, 'comdlg32 error: %s', commdlgerrnames[err]) 89 | return false -- user canceled 90 | end 91 | return true 92 | end 93 | 94 | local dirpaths = { 95 | [''] = 'C:\\Program Files', 96 | [''] = 'C:\\Program Files', 97 | } 98 | local function rundialog(o, issave) 99 | local ofn = ffi.new('OPENFILENAMEW', { 100 | lStructSize = ffi.sizeof'OPENFILENAMEW', 101 | lpstrTitle = winapi.wcs(o.title), 102 | lpstrFile = ffi.new('WCHAR[?]', 260), 103 | nMaxFile = 260, 104 | }) 105 | if o.dir then 106 | ofn.lpstrInitialDir = winapi.wcs(dirpaths[o.dir] or o.dir) 107 | end 108 | if o.filters then 109 | local ft = {} 110 | for _, v in ipairs(o.filters) do 111 | if v == '' then 112 | table.insert(ft, 'All Files') 113 | table.insert(ft, '*') 114 | elseif v == '' then 115 | table.insert(ft, 'Applications') 116 | table.insert(ft, '*.com;*.exe;*.bat;*.cmd;*.vbs;*.vbe;*.js;*.jse') 117 | elseif v == '' then 118 | table.insert(ft, 'Icon Files') 119 | table.insert(ft, '*.exe;*.ico;*.bmp;*.gif;*.png;*.jpg;*.jpeg;*.svg') 120 | elseif type(v) == 'table' then 121 | table.insert(ft, v.name) 122 | table.insert(ft, v.pattern) 123 | end 124 | end 125 | table.insert(ft, '\0') 126 | ofn.lpstrFilter = winapi.wcs(table.concat(ft, '\0')) 127 | end 128 | local cd = fs.curdir() 129 | if issave then 130 | if not chkcomdlg(CD.GetSaveFileNameW(ofn)) then return end 131 | else 132 | if not chkcomdlg(CD.GetOpenFileNameW(ofn)) then return end 133 | end 134 | fs.chdir(cd) 135 | return winapi.mbs(ofn.lpstrFile) 136 | end 137 | 138 | function dialog.open(o) 139 | o = o or {} 140 | o.title = o.title or 'Open file' 141 | o.filters = o.filters or { '' } 142 | return rundialog(o) 143 | end 144 | 145 | function dialog.save(o) 146 | o = o or {} 147 | o.title = o.title or 'Save file' 148 | o.filters = o.filters or { '' } 149 | return rundialog(o, true) 150 | end 151 | 152 | function dialog.folder(o) 153 | -- SHBrowseForFolder 154 | error'NYI' 155 | end 156 | 157 | return dialog 158 | -------------------------------------------------------------------------------- /nativeapp/data/json.lua: -------------------------------------------------------------------------------- 1 | local json = {} 2 | 3 | local function kindof(obj) 4 | if type(obj) ~= 'table' then return type(obj) end 5 | local i = 1 6 | for _ in pairs(obj) do 7 | if obj[i] ~= nil then i = i + 1 else return 'table' end 8 | end 9 | if i == 1 then return 'table' else return 'array' end 10 | end 11 | 12 | local function escapestr(s) 13 | local inchar = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} 14 | local outchar = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} 15 | for i, c in ipairs(inchar) do 16 | s = s:gsub(c, '\\'..outchar[i]) 17 | end 18 | return s 19 | end 20 | 21 | function json.stringify(obj, askey) 22 | local s = {} 23 | local kind = kindof(obj) 24 | if kind == 'array' then 25 | if askey then error('Can\'t encode array as key.') end 26 | s[#s + 1] = '[' 27 | for i, val in ipairs(obj) do 28 | if i > 1 then s[#s + 1] = ', ' end 29 | s[#s + 1] = json.stringify(val) 30 | end 31 | s[#s + 1] = ']' 32 | elseif kind == 'table' then 33 | if askey then error('Can\'t encode table as key.') end 34 | s[#s + 1] = '{' 35 | for k, v in pairs(obj) do 36 | if #s > 1 then s[#s + 1] = ', ' end 37 | s[#s + 1] = json.stringify(k, true) 38 | s[#s + 1] = ':' 39 | s[#s + 1] = json.stringify(v) 40 | end 41 | s[#s + 1] = '}' 42 | elseif kind == 'string' then 43 | return '"'..escapestr(obj)..'"' 44 | elseif kind == 'number' then 45 | if askey then return '"'..tostring(obj)..'"' end 46 | return tostring(obj) 47 | elseif kind == 'boolean' then 48 | return tostring(obj) 49 | elseif kind == 'nil' then 50 | return 'null' 51 | else 52 | error('Unjsonifiable type: '..kind..'.') 53 | end 54 | return table.concat(s) 55 | end 56 | 57 | -- returns pos, didfind; there are two cases: 58 | -- 1. delimiter found: pos = pos after leading space + delim; didfind = true 59 | -- 2. delimiter not found: pos = pos after leading space; didfind = false 60 | -- this throws an error if errifmissing is true and the delim is not found 61 | local function skipdelim(str, pos, delim, errifmissing) 62 | pos = pos + #str:match('^%s*', pos) 63 | if str:sub(pos, pos) ~= delim then 64 | if errifmissing then 65 | error('Expected '..delim..' near position '..pos) 66 | end 67 | return pos, false 68 | end 69 | return pos + 1, true 70 | end 71 | 72 | -- expects the given pos to be the first character after the opening quote 73 | -- returns val, pos; the returned pos is after the closing quote character 74 | local function parsestrval(str, pos, val) 75 | val = val or '' 76 | local earlyenderror = 'End of input found while parsing string.' 77 | if pos > #str then error(earlyenderror) end 78 | local c = str:sub(pos, pos) 79 | if c == '"' then return val, pos + 1 end 80 | if c ~= '\\' then return parsestrval(str, pos + 1, val..c) end 81 | -- we must have a \ character 82 | local escmap = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} 83 | local nextc = str:sub(pos + 1, pos + 1) 84 | if not nextc then error(earlyenderror) end 85 | return parsestrval(str, pos + 2, val..(escmap[nextc] or nextc)) 86 | end 87 | 88 | -- returns val, pos; the returned pos is after the number's final character 89 | local function parsenumval(str, pos) 90 | local numstr = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) 91 | local val = tonumber(numstr) 92 | if not val then error('Error parsing number at position '..pos..'.') end 93 | return val, pos + #numstr 94 | end 95 | 96 | json.null = {} -- represents null 97 | 98 | function json.parse(str, pos, enddelim) 99 | pos = pos or 1 100 | if pos > #str then error('Reached unexpected end of input.') end 101 | pos = pos + #str:match('^%s*', pos) -- skip whitespace 102 | local first = str:sub(pos, pos) 103 | if first == '{' then -- object 104 | local obj, delimfound = {}, true 105 | local key 106 | pos = pos + 1 107 | while true do 108 | key, pos = json.parse(str, pos, '}') 109 | if key == nil then return obj, pos end 110 | if not delimfound then error('Comma missing between object items.') end 111 | pos = skipdelim(str, pos, ':', true) -- true -> error if missing 112 | obj[key], pos = json.parse(str, pos) 113 | pos, delimfound = skipdelim(str, pos, ',') 114 | end 115 | elseif first == '[' then -- array 116 | local arr, delimfound = {}, true 117 | local val 118 | pos = pos + 1 119 | while true do 120 | val, pos = json.parse(str, pos, ']') 121 | if val == nil then return arr, pos end 122 | if not delimfound then error('Comma missing between array items.') end 123 | arr[#arr + 1] = val 124 | pos, delimfound = skipdelim(str, pos, ',') 125 | end 126 | elseif first == '"' then -- string 127 | return parsestrval(str, pos + 1) 128 | elseif first == '-' or first:match('%d') then -- number 129 | return parsenumval(str, pos) 130 | elseif first == enddelim then -- end of object or array 131 | return nil, pos + 1 132 | else -- true/false/null 133 | local lits = { ['true'] = true, ['false'] = false, null = json.null } 134 | for litstr, litval in pairs(lits) do 135 | local litend = pos + #litstr - 1 136 | if str:sub(pos, litend) == litstr then return litval, litend + 1 end 137 | end 138 | local posinfostr = 'position '..pos..': '..str:sub(pos, pos + 10) 139 | error('Invalid json syntax starting at '..posinfostr) 140 | end 141 | end 142 | 143 | return json 144 | -------------------------------------------------------------------------------- /extension/awesomplete.min.js: -------------------------------------------------------------------------------- 1 | // Awesomplete - Lea Verou - MIT license 2 | !function(){function t(t){var e=Array.isArray(t)?{label:t[0],value:t[1]}:"object"==typeof t&&"label"in t&&"value"in t?t:{label:t,value:t};this.label=e.label||e.value,this.value=e.value}function e(t,e,i){for(var n in e){var s=e[n],r=t.input.getAttribute("data-"+n.toLowerCase());"number"==typeof s?t[n]=parseInt(r):!1===s?t[n]=null!==r:s instanceof Function?t[n]=null:t[n]=r,t[n]||0===t[n]||(t[n]=n in i?i[n]:s)}}function i(t,e){return"string"==typeof t?(e||document).querySelector(t):t||null}function n(t,e){return o.call((e||document).querySelectorAll(t))}function s(){n("input.awesomplete").forEach(function(t){new r(t)})}var r=function(t,n){var s=this;this.isOpened=!1,this.input=i(t),this.input.setAttribute("autocomplete","off"),this.input.setAttribute("aria-autocomplete","list"),n=n||{},e(this,{minChars:2,maxItems:10,autoFirst:!1,data:r.DATA,filter:r.FILTER_CONTAINS,sort:!1!==n.sort&&r.SORT_BYLENGTH,item:r.ITEM,replace:r.REPLACE},n),this.index=-1,this.container=i.create("div",{className:"awesomplete",around:t}),this.ul=i.create("ul",{hidden:"hidden",inside:this.container}),this.status=i.create("span",{className:"visually-hidden",role:"status","aria-live":"assertive","aria-relevant":"additions",inside:this.container}),this._events={input:{input:this.evaluate.bind(this),blur:this.close.bind(this,{reason:"blur"}),keydown:function(t){var e=t.keyCode;s.opened&&(13===e&&s.selected?(t.preventDefault(),s.select()):27===e?s.close({reason:"esc"}):38!==e&&40!==e||(t.preventDefault(),s[38===e?"previous":"next"]()))}},form:{submit:this.close.bind(this,{reason:"submit"})},ul:{mousedown:function(t){var e=t.target;if(e!==this){for(;e&&!/li/i.test(e.nodeName);)e=e.parentNode;e&&0===t.button&&(t.preventDefault(),s.select(e,t.target))}}}},i.bind(this.input,this._events.input),i.bind(this.input.form,this._events.form),i.bind(this.ul,this._events.ul),this.input.hasAttribute("list")?(this.list="#"+this.input.getAttribute("list"),this.input.removeAttribute("list")):this.list=this.input.getAttribute("data-list")||n.list||[],r.all.push(this)};r.prototype={set list(t){if(Array.isArray(t))this._list=t;else if("string"==typeof t&&t.indexOf(",")>-1)this._list=t.split(/\s*,\s*/);else if((t=i(t))&&t.children){var e=[];o.apply(t.children).forEach(function(t){if(!t.disabled){var i=t.textContent.trim(),n=t.value||i,s=t.label||i;""!==n&&e.push({label:s,value:n})}}),this._list=e}document.activeElement===this.input&&this.evaluate()},get selected(){return this.index>-1},get opened(){return this.isOpened},close:function(t){this.opened&&(this.ul.setAttribute("hidden",""),this.isOpened=!1,this.index=-1,i.fire(this.input,"awesomplete-close",t||{}))},open:function(){this.ul.removeAttribute("hidden"),this.isOpened=!0,this.autoFirst&&-1===this.index&&this.goto(0),i.fire(this.input,"awesomplete-open")},destroy:function(){i.unbind(this.input,this._events.input),i.unbind(this.input.form,this._events.form);var t=this.container.parentNode;t.insertBefore(this.input,this.container),t.removeChild(this.container),this.input.removeAttribute("autocomplete"),this.input.removeAttribute("aria-autocomplete");var e=r.all.indexOf(this);-1!==e&&r.all.splice(e,1)},next:function(){var t=this.ul.children.length;this.goto(this.index-1&&e.length>0&&(e[t].setAttribute("aria-selected","true"),this.status.textContent=e[t].textContent,this.ul.scrollTop=e[t].offsetTop-this.ul.clientHeight+e[t].clientHeight,i.fire(this.input,"awesomplete-highlight",{text:this.suggestions[this.index]}))},select:function(t,e){if(t?this.index=i.siblingIndex(t):t=this.ul.children[this.index],t){var n=this.suggestions[this.index];i.fire(this.input,"awesomplete-select",{text:n,origin:e||t})&&(this.replace(n),this.close({reason:"select"}),i.fire(this.input,"awesomplete-selectcomplete",{text:n}))}},evaluate:function(){var e=this,i=this.input.value;i.length>=this.minChars&&this._list.length>0?(this.index=-1,this.ul.innerHTML="",this.suggestions=this._list.map(function(n){return new t(e.data(n,i))}).filter(function(t){return e.filter(t,i)}),!1!==this.sort&&(this.suggestions=this.suggestions.sort(this.sort)),this.suggestions=this.suggestions.slice(0,this.maxItems),this.suggestions.forEach(function(t){e.ul.appendChild(e.item(t,i))}),0===this.ul.children.length?this.close({reason:"nomatches"}):this.open()):this.close({reason:"nomatches"})}},r.all=[],r.FILTER_CONTAINS=function(t,e){return RegExp(i.regExpEscape(e.trim()),"i").test(t)},r.FILTER_STARTSWITH=function(t,e){return RegExp("^"+i.regExpEscape(e.trim()),"i").test(t)},r.SORT_BYLENGTH=function(t,e){return t.length!==e.length?t.length-e.length:t$&"),"aria-selected":"false"})},r.REPLACE=function(t){this.input.value=t.value},r.DATA=function(t){return t},Object.defineProperty(t.prototype=Object.create(String.prototype),"length",{get:function(){return this.label.length}}),t.prototype.toString=t.prototype.valueOf=function(){return""+this.label};var o=Array.prototype.slice;i.create=function(t,e){var n=document.createElement(t);for(var s in e){var r=e[s];if("inside"===s)i(r).appendChild(n);else if("around"===s){var o=i(r);o.parentNode.insertBefore(n,o),n.appendChild(o)}else s in n?n[s]=r:n.setAttribute(s,r)}return n},i.bind=function(t,e){if(t)for(var i in e){var n=e[i];i.split(/\s+/).forEach(function(e){t.addEventListener(e,n)})}},i.unbind=function(t,e){if(t)for(var i in e){var n=e[i];i.split(/\s+/).forEach(function(e){t.removeEventListener(e,n)})}},i.fire=function(t,e,i){var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!0);for(var s in i)n[s]=i[s];return t.dispatchEvent(n)},i.regExpEscape=function(t){return t.replace(/[-\\^$*+?.()|[\]{}]/g,"\\$&")},i.siblingIndex=function(t){for(var e=0;t=t.previousElementSibling;e++);return e},"undefined"!=typeof Document&&("loading"!==document.readyState?s():document.addEventListener("DOMContentLoaded",s)),r.$=i,r.$$=n,"undefined"!=typeof self&&(self.Awesomplete=r),"object"==typeof module&&module.exports&&(module.exports=r)}(); 3 | //# sourceMappingURL=awesomplete.min.js.map 4 | -------------------------------------------------------------------------------- /extension/browser-polyfill.min.js: -------------------------------------------------------------------------------- 1 | (function(a,b){if("function"==typeof define&&define.amd)define("webextension-polyfill",["module"],b);else if("undefined"!=typeof exports)b(module);else{var c={exports:{}};b(c),a.browser=c.exports}})(this,function(a){"use strict";if("undefined"==typeof browser){a.exports=(()=>{const c={alarms:{clear:{minArgs:0,maxArgs:1},clearAll:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getAll:{minArgs:0,maxArgs:0}},bookmarks:{create:{minArgs:1,maxArgs:1},export:{minArgs:0,maxArgs:0},get:{minArgs:1,maxArgs:1},getChildren:{minArgs:1,maxArgs:1},getRecent:{minArgs:1,maxArgs:1},getTree:{minArgs:0,maxArgs:0},getSubTree:{minArgs:1,maxArgs:1},import:{minArgs:0,maxArgs:0},move:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeTree:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}},browserAction:{getBadgeBackgroundColor:{minArgs:1,maxArgs:1},getBadgeText:{minArgs:1,maxArgs:1},getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},setIcon:{minArgs:1,maxArgs:1}},commands:{getAll:{minArgs:0,maxArgs:0}},contextMenus:{update:{minArgs:2,maxArgs:2},remove:{minArgs:1,maxArgs:1},removeAll:{minArgs:0,maxArgs:0}},cookies:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:1,maxArgs:1},getAllCookieStores:{minArgs:0,maxArgs:0},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},devtools:{inspectedWindow:{eval:{minArgs:1,maxArgs:2}},panels:{create:{minArgs:3,maxArgs:3,singleCallbackArg:!0}}},downloads:{download:{minArgs:1,maxArgs:1},cancel:{minArgs:1,maxArgs:1},erase:{minArgs:1,maxArgs:1},getFileIcon:{minArgs:1,maxArgs:2},open:{minArgs:1,maxArgs:1},pause:{minArgs:1,maxArgs:1},removeFile:{minArgs:1,maxArgs:1},resume:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1},show:{minArgs:1,maxArgs:1}},extension:{isAllowedFileSchemeAccess:{minArgs:0,maxArgs:0},isAllowedIncognitoAccess:{minArgs:0,maxArgs:0}},history:{addUrl:{minArgs:1,maxArgs:1},getVisits:{minArgs:1,maxArgs:1},deleteAll:{minArgs:0,maxArgs:0},deleteRange:{minArgs:1,maxArgs:1},deleteUrl:{minArgs:1,maxArgs:1},search:{minArgs:1,maxArgs:1}},i18n:{detectLanguage:{minArgs:1,maxArgs:1},getAcceptLanguages:{minArgs:0,maxArgs:0}},idle:{queryState:{minArgs:1,maxArgs:1}},management:{get:{minArgs:1,maxArgs:1},getAll:{minArgs:0,maxArgs:0},getSelf:{minArgs:0,maxArgs:0},uninstallSelf:{minArgs:0,maxArgs:1}},notifications:{clear:{minArgs:1,maxArgs:1},create:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:0},getPermissionLevel:{minArgs:0,maxArgs:0},update:{minArgs:2,maxArgs:2}},pageAction:{getPopup:{minArgs:1,maxArgs:1},getTitle:{minArgs:1,maxArgs:1},hide:{minArgs:0,maxArgs:0},setIcon:{minArgs:1,maxArgs:1},show:{minArgs:0,maxArgs:0}},runtime:{getBackgroundPage:{minArgs:0,maxArgs:0},getBrowserInfo:{minArgs:0,maxArgs:0},getPlatformInfo:{minArgs:0,maxArgs:0},openOptionsPage:{minArgs:0,maxArgs:0},requestUpdateCheck:{minArgs:0,maxArgs:0},sendMessage:{minArgs:1,maxArgs:3},sendNativeMessage:{minArgs:2,maxArgs:2},setUninstallURL:{minArgs:1,maxArgs:1}},storage:{local:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}},managed:{get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1}},sync:{clear:{minArgs:0,maxArgs:0},get:{minArgs:0,maxArgs:1},getBytesInUse:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},set:{minArgs:1,maxArgs:1}}},tabs:{create:{minArgs:1,maxArgs:1},captureVisibleTab:{minArgs:0,maxArgs:2},detectLanguage:{minArgs:0,maxArgs:1},duplicate:{minArgs:1,maxArgs:1},executeScript:{minArgs:1,maxArgs:2},get:{minArgs:1,maxArgs:1},getCurrent:{minArgs:0,maxArgs:0},getZoom:{minArgs:0,maxArgs:1},getZoomSettings:{minArgs:0,maxArgs:1},highlight:{minArgs:1,maxArgs:1},insertCSS:{minArgs:1,maxArgs:2},move:{minArgs:2,maxArgs:2},reload:{minArgs:0,maxArgs:2},remove:{minArgs:1,maxArgs:1},query:{minArgs:1,maxArgs:1},removeCSS:{minArgs:1,maxArgs:2},sendMessage:{minArgs:2,maxArgs:3},setZoom:{minArgs:1,maxArgs:2},setZoomSettings:{minArgs:1,maxArgs:2},update:{minArgs:1,maxArgs:2}},webNavigation:{getAllFrames:{minArgs:1,maxArgs:1},getFrame:{minArgs:1,maxArgs:1}},webRequest:{handlerBehaviorChanged:{minArgs:0,maxArgs:0}},windows:{create:{minArgs:0,maxArgs:1},get:{minArgs:1,maxArgs:2},getAll:{minArgs:0,maxArgs:1},getCurrent:{minArgs:0,maxArgs:1},getLastFocused:{minArgs:0,maxArgs:1},remove:{minArgs:1,maxArgs:1},update:{minArgs:2,maxArgs:2}}};if(0===Object.keys(c).length)throw new Error("api-metadata.json has not been included in browser-polyfill");class d extends WeakMap{constructor(o,p=void 0){super(p),this.createItem=o}get(o){return this.has(o)||this.set(o,this.createItem(o)),super.get(o)}}const e=o=>{return o&&"object"==typeof o&&"function"==typeof o.then},f=(o,p)=>{return(...q)=>{chrome.runtime.lastError?o.reject(chrome.runtime.lastError):p.singleCallbackArg||1===q.length?o.resolve(q[0]):o.resolve(q)}},g=(o,p)=>{const q=r=>1==r?"argument":"arguments";return function(s,...t){if(t.lengthp.maxArgs)throw new Error(`Expected at most ${p.maxArgs} ${q(p.maxArgs)} for ${o}(), got ${t.length}`);return new Promise((u,v)=>{s[o](...t,f({resolve:u,reject:v},p))})}},h=(o,p,q)=>{return new Proxy(p,{apply(r,s,t){return q.call(s,o,...t)}})};let i=Function.call.bind(Object.prototype.hasOwnProperty);const j=(o,p={},q={})=>{let r=Object.create(null),s={has(t,u){return u in t||u in r},get(t,u){if(u in r)return r[u];if(u in t){let w=t[u];if("function"==typeof w){if("function"==typeof p[u])w=h(t,t[u],p[u]);else if(i(q,u)){let x=g(u,q[u]);w=h(t,t[u],x)}else w=w.bind(t);}else if("object"==typeof w&&null!==w&&(i(p,u)||i(q,u)))w=j(w,p[u],q[u]);else return Object.defineProperty(r,u,{configurable:!0,enumerable:!0,get(){return t[u]},set(x){t[u]=x}}),w;return r[u]=w,w}},set(t,u,v){return u in r?r[u]=v:t[u]=v,!0},defineProperty(t,u,v){return Reflect.defineProperty(r,u,v)},deleteProperty(t,u){return Reflect.deleteProperty(r,u)}};return new Proxy(o,s)},l=new d(o=>{return"function"==typeof o?function(q,r,s){let t=o(q,r);return e(t)?(t.then(s,u=>{console.error(u),s(u)}),!0):void(void 0!==t&&s(t))}:o}),m={runtime:{onMessage:(o=>({addListener(p,q,...r){p.addListener(o.get(q),...r)},hasListener(p,q){return p.hasListener(o.get(q))},removeListener(p,q){p.removeListener(o.get(q))}}))(l)}},n=Object.assign({},chrome);return j(n,m,c)})()}else a.exports=browser}); 2 | //# sourceMappingURL=browser-polyfill.min.js.map 3 | 4 | 5 | // webextension-polyfill v.0.2.1 (https://github.com/mozilla/webextension-polyfill) 6 | 7 | /* This Source Code Form is subject to the terms of the Mozilla Public 8 | * License, v. 2.0. If a copy of the MPL was not distributed with this 9 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -------------------------------------------------------------------------------- /nativeapp/data/host.lua: -------------------------------------------------------------------------------- 1 | local host = {} 2 | 3 | local ffi = require'ffi' 4 | local json = require'json' 5 | 6 | local format = string.format 7 | 8 | local WIN = jit.os == 'Windows' 9 | local SEP = WIN and '\\' or '/' 10 | 11 | function host.ping() 12 | return 3 -- nativeapp version 13 | end 14 | 15 | function host.execute(msg) 16 | if not msg.command then return nil, 'command is nil' end 17 | if msg.command == "" then return nil, 'command is empty' end 18 | if WIN and msg.clipboard then require'winapi'.setclipboard(msg.clipboard) end 19 | local tmppath = os.tmpname() 20 | if WIN then tmppath = os.getenv'TMP':gsub('[\\/]+$', SEP)..tmppath end 21 | local f = io.open(tmppath, 'w+') 22 | f:write(format('os.execute[[%s]]', msg.command)) 23 | f:close() 24 | local fmt = WIN and 'start "" /b /d . luajit %q 2>&1' or './luajit %q 2>&1' 25 | f = io.popen(format(fmt, tmppath)) 26 | local s = f:read'*a' 27 | f:close() 28 | os.remove(tmppath) 29 | if not s then return end 30 | return { type='output', message=s:gsub('^%s+', ''):gsub('%s+$', '') } 31 | end 32 | 33 | function host.canbrowse(_) 34 | return pcall(require, 'dialog') 35 | end 36 | 37 | function host.browseexe(msg) 38 | return require'dialog'.open{ 39 | dir = '', 40 | title = msg.title, 41 | filters = { '', '' } 42 | } 43 | end 44 | 45 | function host.browseicon(msg) 46 | msg.path = require'dialog'.open{ 47 | dir = '', 48 | title = msg.title, 49 | filters = {'', ''} 50 | } 51 | return host.pathicon(msg) 52 | end 53 | 54 | function host.pathicon(msg) 55 | local extmimetypes = { 56 | bmp = 'image/bmp', 57 | gif = 'image/gif', 58 | png = 'image/png', 59 | apng = 'image/png', 60 | jpg = 'image/jpeg', 61 | jpeg = 'image/jpeg', 62 | ico = 'image/x-icon', 63 | svg = 'image/svg+xml', 64 | } 65 | local path = msg.path 66 | if path == false then return false end 67 | if not path then return end 68 | local ext = path:match'([^%.]+)$' 69 | local mime = extmimetypes[ext] 70 | local remove = false 71 | if not mime then 72 | if jit.os ~= 'Windows' then return end 73 | local tmppath = os.getenv'TMP'..SEP..'tmpicon.ico' 74 | path = require'winapi'.savefileicon(path, tmppath, msg.large) 75 | if not path then return end 76 | mime = extmimetypes.ico 77 | remove = true 78 | end 79 | local fp = io.open(path, 'rb') 80 | if not fp then return end 81 | local data = fp:read'*a' 82 | if remove then os.remove(path) end 83 | if not data then return end 84 | local b64 = require'base64'.encode(data) 85 | return string.format('data:%s;base64,%s', mime, b64) 86 | end 87 | 88 | local function contains(t, v) 89 | for i=1, #t do if t[i]==v then return true end end; return false 90 | end 91 | function host.exeautocomplete(msg) 92 | local fs = require'fs' 93 | local function isexe(path) 94 | if WIN then 95 | local x = path:match'%.([^.]+)$' 96 | if x=='exe' or x=='com' or x=='bat' or x=='cmd' then return true end 97 | else 98 | local permissions = fs.attr(path, 'permissions') 99 | if permissions and permissions:find'x' then return true end 100 | end 101 | return false 102 | end 103 | if not msg.path or not msg.path:find'[\\/:]' then 104 | local PATH = os.getenv'PATH' 105 | if not PATH then return end 106 | local PATHSEP = WIN and ';' or ':' 107 | local exes = {} 108 | for dir in PATH:gmatch('[^'..PATHSEP..']+') do 109 | if fs.attr(dir, 'mode') == 'directory' then 110 | for name in fs.dir(dir) do 111 | local path = dir:find'[\\/]$' and dir..name or dir..SEP..name 112 | local mode = fs.attr(path, 'mode') 113 | if mode == 'file'and not contains(exes, name) and isexe(path) then 114 | table.insert(exes, name) 115 | end 116 | end 117 | end 118 | end 119 | table.sort(exes) 120 | return exes 121 | end 122 | local dir = msg.path:gsub('(.*[\\/].+)[\\/]$', '%1') 123 | if fs.attr(dir, 'mode') ~= 'directory' then return end 124 | local dirs = {} 125 | local exes = {} 126 | for name in fs.dir(dir) do 127 | local path = dir:find'[\\/]$' and dir..name or dir..SEP..name 128 | local mode = fs.attr(path, 'mode') 129 | if mode == 'directory' and name ~= '.' and name ~= '..' then 130 | table.insert(dirs, path..SEP) 131 | elseif mode == 'file' and isexe(path) then 132 | table.insert(exes, path) 133 | end 134 | end 135 | table.sort(dirs) 136 | table.sort(exes) 137 | local res = {} 138 | for _, v in ipairs(dirs) do table.insert(res, v) end 139 | for _, v in ipairs(exes) do table.insert(res, v) end 140 | return res 141 | end 142 | 143 | if ... == 'test' then 144 | local function test(msg) 145 | local res, err = host[msg.type](msg) 146 | if type(res) == 'table' then 147 | for i = 1, #res do print(res[i]) end 148 | else 149 | print(res, err) 150 | end 151 | io.write'Press enter to continue...' 152 | io.read() 153 | end 154 | test{ type='ping' } 155 | test{ type='execute' } 156 | test{ type='execute', command='' } 157 | if WIN then 158 | test{ type='execute', command='C:\\' } 159 | test{ type='execute', command='H:/black-shades.exe' } 160 | test{ type='execute', command='"C:\\Program Files/Internet Explorer\\iexplore.exe"' } 161 | test{ type='exeautocomplete' } 162 | test{ type='exeautocomplete', path='' } 163 | test{ type='exeautocomplete', path='C:/' } 164 | test{ type='exeautocomplete', path='C:\\Windows' } 165 | test{ type='exeautocomplete', path='C:/Program Files\\' } 166 | else 167 | test{ type='execute', command='/' } 168 | test{ type='execute', command='gnome-terminal' } 169 | test{ type='execute', command='/usr!"£$_)+(^&*£$(&+_}}~¨' } 170 | test{ type='exeautocomplete' } 171 | test{ type='exeautocomplete', path='' } 172 | test{ type='exeautocomplete', path='/' } 173 | test{ type='exeautocomplete', path='/usr/' } 174 | test{ type='exeautocomplete', path='/usr/bin' } 175 | end 176 | test{ type='browseexe' } 177 | test{ type='browseicon' } 178 | return 179 | end 180 | 181 | local function readsz() 182 | while true do 183 | local str = io.stdin:read(4) 184 | if not str then return end 185 | local szp = ffi.cast('uint32_t*', ffi.cast('const char*', str)) 186 | if szp[0] < 4096 then return szp[0] end 187 | io.stdin:read() 188 | end 189 | end 190 | local function readmsg() 191 | while true do 192 | local sz = readsz() 193 | if not sz then return end 194 | local data = json.parse(io.read(sz)) 195 | if type(data) == 'table' then return data end 196 | end 197 | end 198 | local function sendmsg(msg) 199 | if not msg then return end 200 | local str = json.stringify(msg) 201 | local szp = ffi.new('uint32_t[1]', string.len(str)) 202 | io.stdout:write(ffi.string(szp, 4)..str) 203 | io.stdout:flush() 204 | end 205 | 206 | repeat 207 | local msg = readmsg() 208 | if not msg then return end 209 | local fun = host[msg.type] 210 | if fun then 211 | local ok, res, err = pcall(fun, msg) 212 | if not ok or res == nil then 213 | sendmsg{ type = 'error', message = err or res or 'failure' } 214 | else 215 | sendmsg(res) 216 | end 217 | end 218 | until not msg.port 219 | 220 | return host 221 | -------------------------------------------------------------------------------- /extension/yt2p-background.js: -------------------------------------------------------------------------------- 1 | /* 2 | * YouTube 2 Player - Watch videos in an external player. 3 | * 4 | * Copyright 2016-2017 Rasmus Riiner 5 | * 6 | * Distributed under the MIT/X11 License. See LICENSE.md for more details. 7 | */ 8 | 9 | /* global browser */ 10 | 11 | 'use strict' 12 | 13 | const defaults = { 14 | playerGroups: [{ 15 | name: browser.i18n.getMessage('mainGroup'), 16 | icon: browser.extension.getURL('icons/16/yt2p.png'), 17 | players: [{ 18 | name: browser.i18n.getMessage('sendToPlayer'), 19 | icon: browser.extension.getURL('icons/16/player.png'), 20 | command: '', 21 | clipboard: '' 22 | }] 23 | }], 24 | toolsMenuItemsEnabled: true, 25 | iconContextMenuEnabled: true, 26 | iconContextMenuTheme: 'default', // default, light, dark 27 | iconContextMenuIconSize: 16, // 0-256 28 | iconContextMenuPadding: 8, // 0-99 29 | contextMenuItemsEnabled: true, 30 | videoLinkChangeEnabled: true, 31 | videoLinkChangeType: 'underline', // underline, glow, embed, fil 32 | videoLinkClickChangeEnabled: false, 33 | videoLinkClickChangeType: 'send', // send, menu, embed, fil 34 | videoLinkClickPlayerCommand: '', 35 | embeddedVideoChangeEnabled: true, 36 | embeddedVideoChangeType: 'fil', // link, embed, fil 37 | filClickChangeEnabled: false, 38 | filClickChangeType: 'embed' // link, embed 39 | } 40 | 41 | initStorage() 42 | 43 | browser.storage.onChanged.addListener(async changes => { 44 | if (changes.playerGroups) { 45 | await browser.contextMenus.removeAll() 46 | if (changes.playerGroups.newValue) { 47 | installContextMenuItems(changes.playerGroups.newValue) 48 | } 49 | } 50 | if (changes.contextMenuItemsEnabled) { 51 | await browser.contextMenus.removeAll() 52 | if (changes.contextMenuItemsEnabled.newValue) { 53 | const storage = await browser.storage.local.get('playerGroups') 54 | installContextMenuItems(storage.playerGroups) 55 | } 56 | } 57 | }) 58 | 59 | browser.runtime.onInstalled.addListener(details => { 60 | if (details.reason === 'install' || 61 | (details.reason === 'update' && /^1|2\./.test(details.previousVersion))) { 62 | browser.runtime.openOptionsPage() 63 | } 64 | }) 65 | 66 | browser.runtime.onMessage.addListener((message, sender, sendResponse) => { 67 | if (message === 'closeallcontextmenus') { 68 | return closeAllContextMenus() 69 | } else if (message === 'resetstorage') { 70 | return resetStorage() 71 | } else { 72 | doExecute(message) 73 | } 74 | }) 75 | 76 | browser.tabs.onActivated.addListener(closeAllContextMenus) 77 | 78 | async function closeAllContextMenus () { 79 | const tabs = await browser.tabs.query({}) 80 | return Promise.all(tabs.map(async (tab) => { 81 | try { 82 | await browser.tabs.sendMessage(tab.id, 'closecontextmenus') 83 | } catch (e) {} 84 | })) 85 | } 86 | 87 | async function initStorage () { 88 | let storage = await browser.storage.local.get(defaults) 89 | await browser.storage.local.set(storage) 90 | installContextMenuItems(storage.playerGroups) 91 | } 92 | 93 | async function resetStorage () { 94 | await browser.storage.local.clear() 95 | await browser.storage.local.set(defaults) 96 | await browser.contextMenus.removeAll() 97 | installContextMenuItems(defaults.playerGroups) 98 | } 99 | 100 | function doExecute ({command, clipboard, url}) { 101 | if (!command && !clipboard) { 102 | createNotification(browser.i18n.getMessage('commandNotSet')) 103 | browser.runtime.openOptionsPage() 104 | return 105 | } 106 | const message = { type: 'execute' } 107 | if (command) message.command = formatPattern(command, url) 108 | if (clipboard) message.clipboard = formatPattern(clipboard, url) 109 | const startTime = Date.now() 110 | browser.runtime.sendNativeMessage('ee.sumzary.yt2p', message).then(response => { 111 | if (response === true) return 112 | if (Date.now() - startTime > 7500) return 113 | console.log(response.message) 114 | createNotification(response.message) 115 | }).catch(error => { 116 | console.log(error) 117 | createNotification(browser.i18n.getMessage('nativeAppMissing')) 118 | browser.runtime.openOptionsPage() 119 | }) 120 | 121 | function formatPattern (pattern, linkUrl) { 122 | return pattern 123 | .replace('LINKURL', linkUrl) 124 | .replace('VIDEOURL', getStandardisedVideoUrl(linkUrl)) 125 | .replace('VIDEOID', getVideoIdFromUrl(linkUrl)) 126 | .replace('PLAYLISTID', getPlaylistIdFromUrl(linkUrl)) 127 | } 128 | 129 | function getVideoIdFromUrl (videoUrl) { 130 | const re = /v=|v%3[Dd]|youtu.be\/|\/v\/|\/embed\/|img.youtube.com\/vi\/|attribution_link=/ig 131 | const temp = videoUrl.replace(re, '$IDSTART$') 132 | return temp.substr(temp.indexOf('$IDSTART$') + 9, 11) 133 | } 134 | 135 | function getPlaylistIdFromUrl (videoUrl) { 136 | if (!videoUrl.includes('list=')) return '' 137 | const temp = videoUrl.replace('list=', '$PLAYLISTID$') 138 | return temp.substr(temp.indexOf('$PLAYLISTID$') + 11, 11) 139 | } 140 | 141 | function getStandardisedVideoUrl (videoUrl) { 142 | const result = `https://www.youtube.com/watch?v=${getVideoIdFromUrl(videoUrl)}` 143 | const playlistId = getPlaylistIdFromUrl(videoUrl) 144 | if (playlistId) return result + `&list=${playlistId}` 145 | return result 146 | } 147 | } 148 | 149 | async function installContextMenuItems (playerGroups) { 150 | for (const group of playerGroups) { 151 | installPlayerGroupContextMenuItems(group, playerGroups.length === 1) 152 | } 153 | 154 | function installPlayerGroupContextMenuItems (group, isOnlyChild) { 155 | if (group.isSeparator) { 156 | return createContextMenuItem({ type: 'separator' }) 157 | } 158 | const itemId = isOnlyChild ? undefined : createContextMenuItem({ 159 | title: group.name, 160 | icons: { '16': group.icon } 161 | }) 162 | for (const player of group.players) { 163 | installPlayerContextMenuItems(player, itemId) 164 | } 165 | } 166 | 167 | function installPlayerContextMenuItems (player, parentId) { 168 | if (player.isSeparator) { 169 | return createContextMenuItem({ type: 'separator', parentId }) 170 | } 171 | createContextMenuItem({ 172 | title: player.name, 173 | icons: { '16': player.icon }, 174 | parentId, 175 | onclick: info => { 176 | player.url = info.linkUrl || info.srcUrl || info.frameUrl || info.pageUrl 177 | doExecute(player) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | function createNotification (message) { 184 | return browser.notifications.create(null, { 185 | type: 'basic', 186 | title: browser.i18n.getMessage('extensionName'), 187 | iconUrl: browser.extension.getURL('icons/64/yt2p.png'), 188 | message: message 189 | }) 190 | } 191 | 192 | function createContextMenuItem (options) { 193 | try { 194 | return browser.contextMenus.create(options) 195 | } catch (e) {} 196 | delete options.icons 197 | return browser.contextMenus.create(options) 198 | } 199 | -------------------------------------------------------------------------------- /extension/yt2p-content.css: -------------------------------------------------------------------------------- 1 | .yt2p-menugroup > .menuitem-iconic > .menu-accel-container, 2 | .yt2p-menugroup > .menuitem-iconic > .menu-iconic-text, 3 | .yt2p-replaced { 4 | display: none !important; 5 | } 6 | .yt2p-clearall { 7 | clear: both !important; 8 | display: block !important; 9 | } 10 | .yt2p-underline:not(.content-link):not(.related-playlist), 11 | .yt2p-underline.content-link > .title, 12 | .yt2p-underline.related-playlist > .title { 13 | text-decoration: underline dotted red !important; 14 | } 15 | .yt2p-glow:not(.content-link):not(.related-playlist), 16 | .yt2p-glow.content-link > .title, 17 | .yt2p-glow.related-playlist > .title { 18 | text-shadow: 1px 1px 3px red !important; 19 | text-decoration: none; 20 | } 21 | .yt2p-underline:not(.content-link):not(.related-playlist):hover, 22 | .yt2p-underline.content-link:hover > .title, 23 | .yt2p-underline.related-playlist:hover > .title { 24 | text-decoration-style: solid !important; 25 | } 26 | .yt2p-glow:not(.ytp-title-link), 27 | .yt2p-glow.content-link > .title, 28 | .yt2p-glow.related-playlist > .title { 29 | color: black !important; 30 | } 31 | .yt-simple-endpoint, 32 | ytd-video-meta-block, 33 | .ytd-badge-supported-renderer { 34 | text-decoration-line: none !important; 35 | text-decoration: none !important; 36 | } 37 | .yt2p-send-override { 38 | text-decoration: none; 39 | } 40 | .yt2p-page-title { 41 | font-size: 1.8rem; 42 | display: block; 43 | } 44 | 45 | .yt2p-clearall, 46 | .yt2p-video, 47 | .yt2p-video-link, 48 | .yt2p-video-image, 49 | .yt2p-channel-link, 50 | .yt2p-channel-image, 51 | .yt2p-yt-logo, 52 | .yt2p-yt-error, 53 | .yt2p-blackbox, 54 | .yt2p-votebar, 55 | .yt2p-text { 56 | margin: 0 !important; 57 | padding: 0 !important; 58 | border: 0 !important; 59 | opacity: 1 !important; 60 | } 61 | .yt2p-video-parent { 62 | padding-top: 0 !important; 63 | overflow: visible !important; 64 | } 65 | .yt2p-video { 66 | display: inline-block !important; 67 | position: relative !important; 68 | /*float: left !important;*/ 69 | width: 480px !important; 70 | max-width: 480px !important; 71 | margin: 2px 0 !important; 72 | box-sizing: content-box !important; 73 | background-color: black !important; 74 | } 75 | .yt2p-video-link, 76 | .yt2p-video-image { 77 | display: inline-block !important; 78 | position: absolute !important; 79 | top: 0 !important; 80 | left: 0 !important; 81 | width: 480px !important; 82 | height: 360px !important; 83 | max-width: 480px !important; 84 | max-height: 360px !important; 85 | } 86 | .yt2p-channel-link, 87 | .yt2p-channel-image { 88 | display: inline-block !important; 89 | position: absolute !important; 90 | top: 0 !important; 91 | right: 0 !important; 92 | width: 46px !important; 93 | height: 46px !important; 94 | max-width: 46px !important; 95 | max-height: 46px !important; 96 | } 97 | .yt2p-video.yt2p-embedded { 98 | height: 270px !important; 99 | max-height: 270px !important; 100 | } 101 | .yt2p-video.yt2p-fil { 102 | height: 360px !important; 103 | max-height: 360px !important; 104 | /*border: 1px dotted red !important;*/ 105 | } 106 | .yt2p-video.yt2p-fil:hover { 107 | /*border: 1px solid red !important;*/ 108 | box-shadow: 1px 1px 12px red !important; 109 | } 110 | .yt2p-video.yt2p-thunderbird { 111 | margin: 16px 10px !important; 112 | } 113 | .yt2p-video.yt2p-error, 114 | .yt2p-video.yt2p-error > .yt2p-video-link { 115 | height: 45px !important; 116 | max-height: 45px !important; 117 | } 118 | /*.yt2p-yt-logo { 119 | display: inline-block !important; 120 | position: absolute !important; 121 | width: 50px !important; 122 | height: 25px !important; 123 | max-width: 50px !important; 124 | max-height: 25px !important; 125 | background: none !important; 126 | left: 415px !important; 127 | top: 10px !important; 128 | } 129 | .yt2p-yt-error { 130 | display: block !important; 131 | border: 0px !important; 132 | background: none !important; 133 | padding: 0 0 0 0 !important; 134 | margin: 0 0 0 0 !important; 135 | position: absolute !important; 136 | left: 420px !important; 137 | top: 11px !important; 138 | width: auto !important; 139 | height: auto !important; 140 | }*/ 141 | .yt2p-blackbox { 142 | display: inline-block !important; 143 | position: absolute !important; 144 | width: 480px !important; 145 | height: 46px !important; 146 | max-width: 480px !important; 147 | max-height: 46px !important; 148 | background-color: black !important; 149 | opacity: .8 !important; 150 | } 151 | .yt2p-blackbox.yt2p-upper { 152 | left: 0 !important; 153 | top: 0 !important; 154 | } 155 | .yt2p-blackbox.yt2p-lower { 156 | left: 0 !important; 157 | bottom: 0 !important; 158 | } 159 | .yt2p-votebar { 160 | position: absolute !important; 161 | bottom: 0 !important; 162 | height: 5px !important; 163 | } 164 | .yt2p-votebar.yt2p-likes { 165 | left: 0 !important; 166 | background-color: #070 !important; 167 | } 168 | .yt2p-votebar.yt2p-dislikes { 169 | right: 0 !important; 170 | background-color: #700 !important; 171 | } 172 | .yt2p-text { 173 | font-family: Ubuntu, Roboto, Arial, Helvetica, sans-serif !important; 174 | font-size: 12px !important; 175 | font-weight: normal !important; 176 | color: #EEE !important; 177 | position: absolute !important; 178 | line-height: 1em !important; 179 | } 180 | .yt2p-text.yt2p-title { 181 | font-size: 16px !important; 182 | font-weight: bold !important; 183 | text-align: left !important; 184 | text-overflow: ellipsis !important; 185 | width: 400px !important; 186 | max-width: 400px !important; 187 | white-space: nowrap !important; 188 | overflow: hidden !important; 189 | left: 15px !important; 190 | top: 7px !important; 191 | line-height: 2em !important; 192 | } 193 | .yt2p-text.yt2p-views { 194 | text-align: left !important; 195 | width: 150px !important; 196 | max-width: 150px !important; 197 | bottom: 18px !important; 198 | left: 20px !important; 199 | } 200 | .yt2p-text.yt2p-published { 201 | text-align: center !important; 202 | bottom: 18px !important; 203 | } 204 | .yt2p-text.yt2p-duration { 205 | text-align: right !important; 206 | width: 100px !important; 207 | max-width: 100px !important; 208 | bottom: 18px !important; 209 | right: 20px !important; 210 | } 211 | 212 | /* google, bing, startpage */ 213 | .yt2p-video-parent.r > .yt2p-remaining-text, 214 | .yt2p-video-parent.clk > .yt2p-remaining-text, 215 | .yt2p-video-parent.result__title > .yt2p-remaining-text { 216 | display: none !important; 217 | } 218 | .yt2p-video-parent.r ~ .s > div:first-child { 219 | display: none !important; 220 | } 221 | .yt2p-video-parent.r ~ .s > div:nth-child(2) { 222 | margin-left: 0 !important; 223 | } 224 | .yt2p-video ~ .detail__body { 225 | float: right !important; 226 | line-height: 0 !important; 227 | width: 40%; 228 | max-height: 500px; 229 | } 230 | 231 | #yt2pIconContextMenu { 232 | display: flex; 233 | flex-direction: column; 234 | z-index: 1000; 235 | position: absolute; 236 | background-color: #F0F0F0; 237 | box-shadow: 1px 1px 5px black; 238 | } 239 | #yt2pIconContextMenu[yt2p-theme=light] { 240 | background-color: #F6F6F6; 241 | } 242 | #yt2pIconContextMenu[yt2p-theme=dark] { 243 | background-color: #323232; 244 | } 245 | #yt2pIconContextMenu .yt2p-separator { 246 | margin: var(--yt2p-padding-half); 247 | background-color: #CCCCCC; 248 | } 249 | #yt2pIconContextMenu[yt2p-theme=light] .yt2p-separator { 250 | background-color: #D1D1D1; 251 | } 252 | #yt2pIconContextMenu[yt2p-theme=dark] .yt2p-separator { 253 | background-color: #2B2B2B; 254 | } 255 | 256 | #yt2pIconContextMenu .yt2p-playergroup { 257 | display: flex; 258 | flex-direction: row; 259 | align-items: center; 260 | } 261 | #yt2pIconContextMenu .yt2p-playergroup.yt2p-separator { 262 | height: 1px; 263 | } 264 | #yt2pIconContextMenu .yt2p-player { 265 | display: flex; 266 | justify-content: center; 267 | flex: 1; 268 | } 269 | #yt2pIconContextMenu .yt2p-player.yt2p-separator { 270 | width: 1px; 271 | height: 50%; 272 | } 273 | #yt2pIconContextMenu .yt2p-player:not(.yt2p-separator):hover { 274 | background-color: Highlight; 275 | } 276 | #yt2pIconContextMenu .yt2p-playerbutton { 277 | width: var(--yt2p-iconsize); 278 | height: var(--yt2p-iconsize); 279 | padding: var(--yt2p-padding); 280 | cursor: default; 281 | } 282 | -------------------------------------------------------------------------------- /extension/yt2p-options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | __MSG_options__ 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 |

Add-ons need a native application to run executables directly.

18 |

Please download, extract and install it from here.

19 |
20 |
21 |
22 | 23 |
24 |

Newer native application version available, some features might not work as expected.

25 |

Please download, extract and install it from here.

26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 |
37 | 38 |

__MSG_groups__

39 |
40 | 41 |
42 |
43 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | 54 | 55 | 56 |
57 |
58 |
59 |
60 | 61 |

__MSG_players__

62 |
63 | 64 |
65 |
66 |
67 |
68 |
69 | 70 | 71 | 72 | 73 | 74 |
75 |
76 | 77 |
78 |
79 | 81 | 82 |
83 |
84 | 85 |
86 |
87 |
88 | 89 |

__MSG_menus__

90 |
91 |
92 | 93 | 94 |
95 |
96 | 97 | 98 |
99 |
100 |
101 | 106 | 107 |
108 |
109 | 110 | 111 |
112 |
113 | 114 | 115 |
116 |
117 |
118 |
119 |
120 | 121 |

__MSG_videoLinks__

122 |
123 |
124 | 125 | 126 |
127 |
128 |
129 | 130 | 131 |
132 |
133 | 134 | 135 |
136 |
137 | 138 | 139 |
140 |
141 | 142 | 143 |
144 |
145 |
146 | 147 | 148 |
149 |
150 |
151 | 152 | 154 | 155 | 156 |
157 |
158 | 159 | 160 |
161 |
162 | 163 | 164 |
165 |
166 | 167 | 168 |
169 |
170 |
171 |
172 |
173 | 174 |

__MSG_embeddedVideos__

175 |
176 |
177 | 178 | 179 |
180 |
181 |
182 | 183 | 184 |
185 |
186 | 187 | 188 |
189 |
190 | 191 | 192 |
193 |
194 |
195 |
196 |
197 | 198 |

__MSG_fancyImageLinks__

199 |
200 |
201 | 202 | 203 |
204 |
205 |
206 | 207 | 208 |
209 |
210 | 211 | 212 |
213 |
214 |
215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /nativeapp/data/dialog_gtk3.lua: -------------------------------------------------------------------------------- 1 | local dialog = {} 2 | 3 | local ffi = require'ffi' 4 | 5 | ffi.cdef[[ 6 | 7 | char* gettext(const char*); 8 | 9 | // glib/ 10 | 11 | // gtypes.h 12 | 13 | typedef char gchar; 14 | typedef short gshort; 15 | typedef long glong; 16 | typedef int gint; 17 | typedef gint gboolean; 18 | typedef unsigned char guchar; 19 | typedef unsigned short gushort; 20 | typedef unsigned long gulong; 21 | typedef unsigned int guint; 22 | typedef float gfloat; 23 | typedef double gdouble; 24 | typedef void* gpointer; 25 | typedef const void* gconstpointer; 26 | 27 | // gerror.h 28 | 29 | typedef struct _GError GError; 30 | 31 | // gmem.h 32 | 33 | void g_free(gpointer); 34 | 35 | // gmessages.h 36 | typedef enum { 37 | G_LOG_FLAG_RECURSION = 1 << 0, 38 | G_LOG_FLAG_FATAL = 1 << 1, 39 | G_LOG_LEVEL_ERROR = 1 << 2, 40 | G_LOG_LEVEL_CRITICAL = 1 << 3, 41 | G_LOG_LEVEL_WARNING = 1 << 4, 42 | G_LOG_LEVEL_MESSAGE = 1 << 5, 43 | G_LOG_LEVEL_INFO = 1 << 6, 44 | G_LOG_LEVEL_DEBUG = 1 << 7, 45 | G_LOG_LEVEL_MASK = ~(G_LOG_FLAG_RECURSION | G_LOG_FLAG_FATAL), 46 | } GLogLevelFlags; 47 | typedef void (*GLogFunc)(const gchar* log_domain, 48 | GLogLevelFlags log_level, const gchar* message, gpointer user_data); 49 | guint g_log_set_handler(const gchar* log_domain, 50 | GLogLevelFlags log_levels, GLogFunc, gpointer user_data); 51 | void g_log_remove_handler(const gchar* log_domain, 52 | guint handler_id); 53 | void g_log_default_handler(const gchar* log_domain, 54 | GLogLevelFlags log_level, const gchar* message, gpointer unused_data); 55 | GLogFunc g_log_set_default_handler(GLogFunc, gpointer user_data); 56 | 57 | // gclosure.h 58 | 59 | typedef struct _GClosure GClosure; 60 | typedef void (*GCallback)(); 61 | typedef void (*GClosureNotify)(gpointer data, GClosure*); 62 | 63 | // gsignal.h 64 | 65 | typedef enum { 66 | G_CONNECT_AFTER = 1 << 0, 67 | G_CONNECT_SWAPPED = 1 << 1, 68 | } GConnectFlags; 69 | gulong g_signal_connect_data(gpointer instance, 70 | const gchar* detailed_signal, GCallback handler, 71 | gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags); 72 | typedef void (*response_proc)(void* obj, int id, void* userdata); 73 | gulong _connect_response(gpointer instance, 74 | const gchar* detailed_signal, response_proc handler, 75 | gpointer data, GClosureNotify destroy_data, 76 | GConnectFlags connect_flags) asm("g_signal_connect_data"); 77 | 78 | // gobject.h 79 | 80 | typedef struct _GObject GObject; 81 | void g_object_unref(gpointer); 82 | 83 | // gtk/ 84 | 85 | // gtktypes.h 86 | 87 | typedef struct _GtkWidget GtkWidget; 88 | typedef struct _GtkWidgetPath GtkWidgetPath; 89 | typedef struct _GtkWindow GtkWindow; 90 | 91 | // gtkmain.h 92 | 93 | void gtk_init(int* argc, char*** argv); 94 | void gtk_main(); 95 | void gtk_main_quit(); 96 | 97 | // gtkwindow.h 98 | 99 | typedef enum { 100 | GTK_WINDOW_TOPLEVEL, 101 | GTK_WINDOW_POPUP 102 | } GtkWindowType; 103 | typedef enum { 104 | GTK_WIN_POS_NONE, 105 | GTK_WIN_POS_CENTER, 106 | GTK_WIN_POS_MOUSE, 107 | GTK_WIN_POS_CENTER_ALWAYS, 108 | GTK_WIN_POS_CENTER_ON_PARENT 109 | } GtkWindowPosition; 110 | GtkWidget* gtk_window_new(GtkWindowType); 111 | void gtk_window_set_title(GtkWindow*, const gchar*); 112 | const gchar* gtk_window_get_title(GtkWindow*); 113 | void gtk_window_set_wmclass(GtkWindow*, const gchar* name, const gchar* class); 114 | void gtk_window_set_icon_name(GtkWindow*, const gchar*); 115 | gboolean gtk_window_set_icon_from_file(GtkWindow*, const gchar*, GError**); 116 | void gtk_window_set_position(GtkWindow*, GtkWindowPosition); 117 | void gtk_window_set_modal(GtkWindow*, gboolean); 118 | gboolean gtk_window_get_modal(GtkWindow*); 119 | void gtk_window_set_transient_for(GtkWindow*, GtkWindow* parent); 120 | GtkWindow *gtk_window_get_transient_for(GtkWindow *); 121 | 122 | // gtkwidget.h 123 | 124 | void gtk_widget_destroy(GtkWidget*); 125 | void gtk_widget_unparent(GtkWidget*); 126 | void gtk_widget_show(GtkWidget*); 127 | void gtk_widget_hide(GtkWidget*); 128 | void gtk_widget_show_now(GtkWidget*); 129 | void gtk_widget_show_all(GtkWidget*); 130 | void gtk_widget_map(GtkWidget*); 131 | void gtk_widget_unmap(GtkWidget*); 132 | void gtk_widget_realize(GtkWidget*); 133 | void gtk_widget_unrealize(GtkWidget*); 134 | 135 | // gtkcontainer.h 136 | 137 | typedef struct _GtkContainer GtkContainer; 138 | void gtk_container_add(GtkContainer*, GtkWidget*); 139 | void gtk_container_remove(GtkContainer*, GtkWidget*); 140 | 141 | // gtkdialog.h 142 | 143 | typedef struct _GtkDialog GtkDialog; 144 | typedef enum { 145 | GTK_DIALOG_MODAL = 1 << 0, 146 | GTK_DIALOG_DESTROY_WITH_PARENT = 1 << 1, 147 | GTK_DIALOG_USE_HEADER_BAR = 1 << 2, 148 | } GtkDialogFlags; 149 | typedef enum { 150 | GTK_RESPONSE_NONE = -1, 151 | GTK_RESPONSE_REJECT = -2, 152 | GTK_RESPONSE_ACCEPT = -3, 153 | GTK_RESPONSE_DELETE_EVENT = -4, 154 | GTK_RESPONSE_OK = -5, 155 | GTK_RESPONSE_CANCEL = -6, 156 | GTK_RESPONSE_CLOSE = -7, 157 | GTK_RESPONSE_YES = -8, 158 | GTK_RESPONSE_NO = -9, 159 | GTK_RESPONSE_APPLY = -10, 160 | GTK_RESPONSE_HELP = -11, 161 | } GtkResponseType; 162 | gint gtk_dialog_run(GtkDialog*); 163 | 164 | // gtkfilefilter.h 165 | 166 | typedef struct _GtkFileFilter GtkFileFilter; 167 | typedef enum { 168 | GTK_FILE_FILTER_FILENAME = 1 << 0, 169 | GTK_FILE_FILTER_URI = 1 << 1, 170 | GTK_FILE_FILTER_DISPLAY_NAME = 1 << 2, 171 | GTK_FILE_FILTER_MIME_TYPE = 1 << 3, 172 | } GtkFileFilterFlags; 173 | GtkFileFilter* gtk_file_filter_new(); 174 | void gtk_file_filter_set_name(GtkFileFilter*, const gchar* name); 175 | const gchar* gtk_file_filter_get_name(GtkFileFilter*); 176 | void gtk_file_filter_add_mime_type(GtkFileFilter*, const gchar* mime_type); 177 | void gtk_file_filter_add_pattern(GtkFileFilter*, const gchar* pattern); 178 | void gtk_file_filter_add_pixbuf_formats(GtkFileFilter*); 179 | 180 | // gtkfilechooser.h 181 | 182 | typedef struct _GtkFileChooser GtkFileChooser; 183 | typedef enum { 184 | GTK_FILE_CHOOSER_ACTION_OPEN, 185 | GTK_FILE_CHOOSER_ACTION_SAVE, 186 | GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, 187 | GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER 188 | } GtkFileChooserAction; 189 | typedef enum { 190 | GTK_FILE_CHOOSER_CONFIRMATION_CONFIRM, 191 | GTK_FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME, 192 | GTK_FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN 193 | } GtkFileChooserConfirmation; 194 | void gtk_file_chooser_set_local_only(GtkFileChooser*, gboolean); 195 | gboolean gtk_file_chooser_get_local_only(GtkFileChooser*); 196 | void gtk_file_chooser_set_select_multiple(GtkFileChooser*, gboolean); 197 | gboolean gtk_file_chooser_get_select_multiple(GtkFileChooser*); 198 | void gtk_file_chooser_set_show_hidden(GtkFileChooser*, gboolean); 199 | gboolean gtk_file_chooser_get_show_hidden(GtkFileChooser*); 200 | void gtk_file_chooser_set_do_overwrite_confirmation(GtkFileChooser*, gboolean); 201 | gboolean gtk_file_chooser_get_do_overwrite_confirmation(GtkFileChooser*); 202 | void gtk_file_chooser_set_create_folders(GtkFileChooser*, gboolean); 203 | gboolean gtk_file_chooser_get_create_folders(GtkFileChooser*); 204 | void gtk_file_chooser_set_current_name(GtkFileChooser*, const gchar* name); 205 | gchar* gtk_file_chooser_get_current_name(GtkFileChooser*); 206 | gchar* gtk_file_chooser_get_filename(GtkFileChooser*); 207 | gboolean gtk_file_chooser_set_filename(GtkFileChooser*, const char*); 208 | gboolean gtk_file_chooser_select_filename(GtkFileChooser*, const char*); 209 | void gtk_file_chooser_unselect_filename(GtkFileChooser*, const char*); 210 | void gtk_file_chooser_select_all(GtkFileChooser*); 211 | void gtk_file_chooser_unselect_all(GtkFileChooser*); 212 | gboolean gtk_file_chooser_set_current_folder(GtkFileChooser*, const gchar*); 213 | gchar* gtk_file_chooser_get_current_folder(GtkFileChooser*); 214 | void gtk_file_chooser_add_filter(GtkFileChooser*, GtkFileFilter*); 215 | void gtk_file_chooser_remove_filter(GtkFileChooser*, GtkFileFilter*); 216 | void gtk_file_chooser_set_filter(GtkFileChooser*, GtkFileFilter*); 217 | GtkFileFilter* gtk_file_chooser_get_filter(GtkFileChooser*); 218 | 219 | // gtkfilechooserwidget.h 220 | 221 | GtkWidget* gtk_file_chooser_widget_new(GtkFileChooserAction); 222 | 223 | // gtkfilechooserdialog.h 224 | 225 | typedef struct _GtkFileChooserDialog GtkFileChooserDialog; 226 | GtkWidget* gtk_file_chooser_dialog_new(const gchar* title, 227 | GtkWindow *parent, GtkFileChooserAction, 228 | const gchar *first_button_text, ...); 229 | ]] 230 | 231 | local C, cast = ffi.C, ffi.cast 232 | local G = ffi.load'gtk-3' 233 | 234 | -- G.g_log_set_default_handler(function(domain, level, message, userdata) 235 | -- require'ut'.run('zenity --info --text='..ffi.string(message)) 236 | -- end, nil) 237 | G.g_log_set_default_handler(function() end, nil) 238 | G.gtk_init(nil, nil) 239 | 240 | local dirnames = { 241 | [''] = '/usr/bin', 242 | [''] = '/usr/share/icons', 243 | } 244 | local typenames = { 245 | [''] = 'All Files', 246 | [''] = 'Applications', 247 | [''] = 'Image Files', 248 | [''] = 'Icon Files', 249 | ['