mirror of
https://github.com/bytedream/stream-bypass.git
synced 2025-06-27 10:30:31 +02:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
9f0e1b59ce | |||
d928d25e09 | |||
266771aa13 | |||
d56672d90f | |||
a9ea5fe4b2 | |||
bb3f5384d6 | |||
6989587161 | |||
dd9bf71a5c | |||
6da0050df4 | |||
1a7c22ec0e | |||
175862b098 | |||
fd5a532d0f | |||
8c43eedb23 | |||
e027c2e09e | |||
f9a0656d4d | |||
382d8b1268 | |||
5b8639ce6a | |||
841c824590 | |||
2055a3ea81 | |||
0262d1853c | |||
81da6600e6 | |||
817f5b82f9 | |||
9a17fb0d9b | |||
17f8aab216 | |||
44d4c9cbcf | |||
b34531b982 | |||
e699d3885c | |||
396038a803 | |||
bd64d4ed0b | |||
a207c336b0 | |||
2460657f2a | |||
698ed5ac3c | |||
dc42220f09 | |||
e146649bbf | |||
424e34190c |
35
README.md
35
README.md
@ -67,22 +67,25 @@ Install the addon directly from the [firefox addon store](https://addons.mozilla
|
||||
|
||||
## 📜 Supported sites
|
||||
|
||||
| Site | Supported | Note |
|
||||
|-----------------------------------------------------------------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ❌ | URL can be extracted but not played (see [here](https://github.com/ByteDream/stream-bypass/blob/674527ed9c1aa35a30fed135c46017571dd3ce31/src/match/matches.ts#L18) for more information) |
|
||||
| [evoload.io](https://evoload.io) | ✔️ | |
|
||||
| [mixdrop.co](https://mixdrop.co) | ✔ ️ | |
|
||||
| [mp4upload.com](https://mp4upload.com) | ❌ | URL can be extracted but not. Probably the same issue as with doodstream |
|
||||
| [newgrounds.com](https://newgrounds.com) | ✔ | |
|
||||
| [streamtape.com](https://streamtape.com) | ✔ | |
|
||||
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | |
|
||||
| [upstream.to](https://upstream.to) | ✔ | |
|
||||
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time |
|
||||
| [vidlox.me](https://vidlox.me) | ⚠ | Website down / Timeout |
|
||||
| [vidoza.net](https://vidoza.net) | ✔ | |
|
||||
| [vivo.sx](https://vivo.sx) | ⚠️ | Website down / Timeout |
|
||||
| [voe.sx](https://voe.sx) / [voeunblk.com](https://voeunblk.com) | ✔ | |
|
||||
| [vupload.com](https://vupload.com) | ✔ | |
|
||||
| Site | Supported | Note |
|
||||
|-----------------------------------------------------------------------|-----------|-------------------------------------------------------|
|
||||
| [doodstream.com](doodstream.com) / [dood.pm](https://dood.pm) | ✔️ | |
|
||||
| [evoload.io](https://evoload.io) | ⚠️️ | Website down / Timeout |
|
||||
| [filemoon.sx](https://filemoon.sx) | ✔ | |
|
||||
| [mcloud.to](https://mcloud.to/) | ❌ | Reverse engineering the site costs too much time (#5) |
|
||||
| [mixdrop.co](https://mixdrop.co) | ✔ ️ | |
|
||||
| [mp4upload.com](https://mp4upload.com) | ✔ | |
|
||||
| [newgrounds.com](https://newgrounds.com) | ✔ | |
|
||||
| [streamtape.com](https://streamtape.com) | ✔ | |
|
||||
| [streamzz.to](https://streamzz.to) / [streamz.ws](https://streamz.ws) | ✔ | |
|
||||
| [upstream.to](https://upstream.to) | ✔ | |
|
||||
| [videovard.sx](https://videovard.sx) | ❌ | Reverse engineering the site costs too much time |
|
||||
| [vidlox.me](https://vidlox.me) | ⚠ | Website down / Timeout |
|
||||
| [vidoza.net](https://vidoza.net) | ✔ | |
|
||||
| [vistream.pro](https://vidstream.pro) | ❌ | Reverse engineering the site costs too much time (#5) |
|
||||
| [vivo.sx](https://vivo.sx) | ⚠️ | Website down / Timeout |
|
||||
| [voe.sx](https://voe.sx) | ✔ | |
|
||||
| [vupload.com](https://vupload.com) | ✔ | |
|
||||
|
||||
- ✔️: Everything ok.
|
||||
- ⚠: Included in the addon but will probably not work. See `Note` in this case, an explanation why will stand there in the most cases.
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stream-bypass",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.6",
|
||||
"description": "Multi-browser addon for multiple streaming providers which redirects directly to the source video",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
@ -21,12 +21,12 @@
|
||||
"homepage": "https://github.com/ByteDream/stream-bypass#readme",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^22.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||
"@rollup/plugin-node-resolve": "^14.0.1",
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"@types/chrome": "^0.0.190",
|
||||
"@types/chrome": "^0.0.196",
|
||||
"@types/node-sass": "^4.11.2",
|
||||
"@types/yazl": "^2.4.2",
|
||||
"eslint": "^8.17.0",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {getMatch} from "./match/match";
|
||||
import {storageGet, storageSet} from "./store/store";
|
||||
import {storageDelete, storageGet, storageSet} from "./store/store";
|
||||
import {Match} from "./match/matches";
|
||||
|
||||
chrome.webRequest.onBeforeRedirect.addListener(async details => {
|
||||
@ -9,6 +9,8 @@ chrome.webRequest.onBeforeRedirect.addListener(async details => {
|
||||
if ((match = await getMatch(new URL(details.url).host)) !== undefined) {
|
||||
await storageSet('redirect', match.id)
|
||||
}
|
||||
} else {
|
||||
await storageDelete('redirect')
|
||||
}
|
||||
}, {
|
||||
urls: ['<all_urls>'],
|
||||
|
22
src/index.ts
22
src/index.ts
@ -4,23 +4,35 @@ import {Match, matches} from "./match/matches";
|
||||
|
||||
async function main() {
|
||||
let match: Match;
|
||||
let redirect = false;
|
||||
if ((match = await getMatch(window.location.host)) === undefined) {
|
||||
let id: string
|
||||
if ((id = await storageGet('redirect')) !== undefined) {
|
||||
redirect = true
|
||||
match = matches.find(m => m.id === id)
|
||||
await storageDelete('redirect')
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const re = document.body.innerHTML.match(match.regex)
|
||||
if (re === null) {
|
||||
return
|
||||
}
|
||||
if (re === null) return
|
||||
if (redirect) await storageDelete('redirect')
|
||||
|
||||
const url = await match.match(re)
|
||||
location.assign(chrome.runtime.getURL(`ui/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}`))
|
||||
|
||||
if (match.replace && !url.endsWith('.m3u8')) {
|
||||
const player = document.createElement('video')
|
||||
player.style.width = '100%'
|
||||
player.style.height = '100%'
|
||||
player.controls = true
|
||||
player.src = url
|
||||
|
||||
document.body.innerHTML = ''
|
||||
document.body.append(player)
|
||||
} else {
|
||||
window.location.assign(chrome.runtime.getURL(`ui/player/player.html?id=${match.id}&url=${encodeURIComponent(url)}&domain=${window.location.host}`))
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
@ -3,7 +3,7 @@
|
||||
"name": "Stream Bypass",
|
||||
"author": "ByteDream",
|
||||
"description": "A multi-browser addon / extension for multiple streaming providers which redirects directly to the source video.",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.6",
|
||||
"homepage_url": "https://github.com/ByteDream/stream-bypass",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
|
@ -1,5 +1,5 @@
|
||||
export enum Reliability {
|
||||
HIGH = 1,
|
||||
HIGH,
|
||||
NORMAL,
|
||||
LOW,
|
||||
}
|
||||
@ -9,24 +9,24 @@ export abstract class Match {
|
||||
id: string
|
||||
reliability: Reliability
|
||||
domains: string[]
|
||||
replace?: boolean
|
||||
regex: RegExp
|
||||
abstract match(match: RegExpMatchArray): Promise<string>
|
||||
|
||||
notice?: string
|
||||
}
|
||||
|
||||
// DOES NOT WORK.
|
||||
// The url can be extracted (sometimes??? wtf) without problems but to receive the actual video, custom request
|
||||
// headers must be set. And because the javascript and browser ecosystem is so fucked up, there is no good way to
|
||||
// do this with media which can be natively played with the browser, like here.
|
||||
class Doodstream implements Match {
|
||||
name = 'Doodstream'
|
||||
id = 'doodstream'
|
||||
reliability = Reliability.HIGH
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'doodstream.com',
|
||||
'dood.pm'
|
||||
'dood.pm',
|
||||
'dood.ws',
|
||||
'dood.wf'
|
||||
]
|
||||
replace = true
|
||||
regex = new RegExp(/(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)/s)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
@ -65,33 +65,63 @@ class Evoload implements Match {
|
||||
}
|
||||
}
|
||||
|
||||
class Filemoon implements Match {
|
||||
name = 'Filemoon'
|
||||
id = 'filemoon'
|
||||
reliability = Reliability.HIGH
|
||||
domains = [
|
||||
'filemoon.sx'
|
||||
]
|
||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
const start_idx = match.indexOf('moon')
|
||||
|
||||
const prefix = `${match[start_idx]}-${match[start_idx-1]}-${match[start_idx-2]}-${match[start_idx-3]}`
|
||||
const time = match.find(m => m.length === 10 && !isNaN(parseInt(m)))
|
||||
const offset = !isNaN(parseInt(match[start_idx-12])) && parseInt(match[start_idx-12]).toString().length == match[start_idx-12].length ? 0 : -1
|
||||
|
||||
return `https://${prefix}.filemoon.${match[start_idx-4]}/${match[start_idx-5]}/${match[start_idx-6]}/${match[start_idx-7]}/${match[start_idx-8]}/master.m3u8?t=${match[start_idx-11]}${offset != 0 ? `-${match[start_idx-12]}` : ''}&s=${time}&e=${match[start_idx + offset - 12]}&sp=${match[start_idx + offset - 18]}`
|
||||
}
|
||||
}
|
||||
|
||||
class Mixdrop implements Match {
|
||||
name = 'Mixdrop'
|
||||
id = 'mixdrop'
|
||||
reliability = Reliability.HIGH
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'mixdrop.co'
|
||||
]
|
||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://a-${match[1]}.${match[4]}.${match[5]}/v/${match[2]}.${match[6]}?s=${match[12]}&e=${match[13]}`
|
||||
const prefix = /(?<=\/\/)[a|s](?=-)/.exec(document.body.innerHTML)[0]
|
||||
const subdomain = match[1].length < match[2].length ? match[1] : match[2]
|
||||
const domain = match.slice().sort((a, b) => b.length - a.length).find(m => /^[a-z]+$/.test(m))
|
||||
const id = match[1].length > match[2].length ? match[1] : match[2]
|
||||
const tld = match.find(m => ['net', 'io', 'to', 'sx', 'com'].indexOf(m) !== -1)
|
||||
const s = match.slice().sort((a, b) => b.length - a.length).slice(1)[0]
|
||||
const e_t = match.find(m => m.length === 10 && !isNaN(parseInt(m)))
|
||||
|
||||
return `https://${prefix}-${subdomain}.${domain}.${tld}/v/${id}.mp4?s=${s}&e=${e_t}&_t=${e_t}`
|
||||
}
|
||||
}
|
||||
|
||||
/*class Mp4Upload implements Match {
|
||||
class Mp4Upload implements Match {
|
||||
name = 'Mp4Upload'
|
||||
id = 'mp4upload'
|
||||
reliability = Reliability.LOW
|
||||
reliability = Reliability.NORMAL
|
||||
domains = [
|
||||
'mp4upload.com'
|
||||
]
|
||||
replace = true
|
||||
regex = new RegExp(/(?<=\|)\w{2,}/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://${match[34]}.mp4upload.com:${match[89]}/d/${match[88]}/video.mp4`
|
||||
let id = match.slice().reduce((a, b) => a.length >= b.length ? a : b)
|
||||
return `https://www4.mp4upload.com:282/d/${id}/video.mp4`
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
class Newgrounds implements Match {
|
||||
name = 'Newgrounds'
|
||||
@ -121,17 +151,17 @@ class Streamtape implements Match {
|
||||
domains = [
|
||||
'streamtape.com'
|
||||
]
|
||||
regex = new RegExp(/\/get_video\S*(?=')/gm)
|
||||
regex = new RegExp(/id=.*(?=')/gm)
|
||||
|
||||
async match(match: RegExpMatchArray): Promise<string> {
|
||||
return `https://streamtape.com${match[0]}`
|
||||
return `https://streamtape.com/get_video?${match.reverse()[0]}`
|
||||
}
|
||||
}
|
||||
|
||||
class Streamzz implements Match {
|
||||
name = 'Streamzz'
|
||||
id = 'streamzz'
|
||||
reliability = Reliability.NORMAL
|
||||
reliability = Reliability.LOW
|
||||
domains = [
|
||||
'streamzz.to',
|
||||
'streamz.ws'
|
||||
@ -242,8 +272,11 @@ class Vupload implements Match {
|
||||
}
|
||||
|
||||
export const matches = [
|
||||
new Doodstream(),
|
||||
new Evoload(),
|
||||
new Filemoon(),
|
||||
new Mixdrop(),
|
||||
new Mp4Upload(),
|
||||
new Newgrounds(),
|
||||
new Streamtape(),
|
||||
new Streamzz(),
|
||||
|
@ -2,14 +2,16 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>HLS</title>
|
||||
<title>Stream Bypass</title>
|
||||
<link rel="stylesheet" href="player.css">
|
||||
<script src="player.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<video id="video"></video>
|
||||
<div id="message-container">
|
||||
<p id="message" hidden></p>
|
||||
<p id="message"></p>
|
||||
<br>
|
||||
<p>Open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues">here</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,22 +0,0 @@
|
||||
body
|
||||
background-color: #131313
|
||||
|
||||
html, body, video
|
||||
width: 100%
|
||||
height: 100%
|
||||
margin: 0
|
||||
|
||||
video
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
|
||||
#message-container
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
height: 100%
|
||||
|
||||
#message
|
||||
color: white
|
||||
text-align: center
|
34
src/ui/player/player.scss
Normal file
34
src/ui/player/player.scss
Normal file
@ -0,0 +1,34 @@
|
||||
body {
|
||||
background-color: #131313;
|
||||
}
|
||||
|
||||
html, body, video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#message-container {
|
||||
visibility: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
|
||||
& * {
|
||||
visibility: inherit;
|
||||
}
|
||||
|
||||
& a, & a:visited {
|
||||
color: red
|
||||
}
|
||||
}
|
@ -1,20 +1,20 @@
|
||||
import {Match, matches, Reliability} from "../../match/matches";
|
||||
import {matches} from "../../match/matches";
|
||||
// @ts-ignore
|
||||
import Hls from "hls.js";
|
||||
|
||||
function show_message(message: string) {
|
||||
document.getElementById('message').innerText = message
|
||||
document.getElementById('message-container').hidden = false
|
||||
document.getElementById('message-container').style.visibility = 'visible'
|
||||
document.getElementById('video').hidden = true
|
||||
}
|
||||
|
||||
async function play_native(url: string, match: Match) {
|
||||
async function play_native(url: string) {
|
||||
const video = document.getElementById('video') as HTMLVideoElement
|
||||
video.controls = true
|
||||
video.src = url
|
||||
}
|
||||
|
||||
async function play_hls(url: string, match: Match) {
|
||||
async function play_hls(url: string) {
|
||||
const video = document.getElementById('video') as HTMLVideoElement
|
||||
video.controls = true
|
||||
|
||||
@ -26,36 +26,6 @@ async function play_hls(url: string, match: Match) {
|
||||
})
|
||||
hls.loadSource(url)
|
||||
hls.attachMedia(video)
|
||||
|
||||
const loaded = await new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
resolve(false)
|
||||
}, match.reliability * 3000)
|
||||
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
|
||||
if (!loaded) {
|
||||
let message: string
|
||||
switch (match.reliability) {
|
||||
case Reliability.LOW:
|
||||
message = `The reliability for this domain is low, errors like this are common.
|
||||
Try to choose another streaming provider (if existent) or deactivate the addon for this provider (${match.name}) and try again`
|
||||
break
|
||||
case Reliability.NORMAL:
|
||||
message = `The reliability for this domain is normal, errors like this can occur but are not very common. Try to refresh the page`
|
||||
break
|
||||
case Reliability.HIGH:
|
||||
message = `The reliability for this domains is high, errors like this are very unlikely to happen.
|
||||
Try to refresh the page and if the error still exists you might want to open a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>.
|
||||
When you're using <a href="https://www.torproject.org/">Tor</a> such errors have a slight chance to occur more often,
|
||||
so if this is the case just try to reload the page and see if it's working then`
|
||||
break
|
||||
}
|
||||
show_message(`Could not load HLS video. ${message}`)
|
||||
}
|
||||
} else {
|
||||
show_message('Failed to play m3u8 video (hls is not supported). Try again or create a new issue <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>')
|
||||
}
|
||||
@ -64,16 +34,17 @@ async function play_hls(url: string, match: Match) {
|
||||
async function main() {
|
||||
const urlQuery = new URLSearchParams(window.location.search)
|
||||
const id = urlQuery.get('id')
|
||||
const url = urlQuery.get('url')
|
||||
const url = decodeURIComponent(urlQuery.get('url'))
|
||||
const domain = urlQuery.get('domain')
|
||||
|
||||
const match = matches.find((m) => m.id === id)
|
||||
if (match === undefined) {
|
||||
show_message(`Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues/new">here</a>`)
|
||||
show_message(`Invalid id: ${id}. Please report this <a href="https://github.com/ByteDream/stream-bypass/issues">here</a>`)
|
||||
return
|
||||
}
|
||||
document.title = match.name
|
||||
document.title = `Stream Bypass (${domain})`
|
||||
|
||||
url.endsWith('.m3u8') ? await play_hls(url, match) : await play_native(url, match)
|
||||
new URL(url).pathname.endsWith('.m3u8') ? await play_hls(url) : await play_native(url)
|
||||
}
|
||||
|
||||
main()
|
||||
|
@ -17,7 +17,7 @@
|
||||
<hr>
|
||||
<table id="sub-container">
|
||||
</table>
|
||||
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues/new">Something does not work</a>
|
||||
<a id="error" href="https://github.com/ByteDream/stream-bypass/issues">Something does not work</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,56 +0,0 @@
|
||||
body
|
||||
background-color: #2b2a33
|
||||
font-weight: bold
|
||||
max-height: 500px
|
||||
overflow-x: hidden
|
||||
overflow-y: auto
|
||||
|
||||
a, p
|
||||
color: white
|
||||
font-size: 16px
|
||||
margin: 5px 0
|
||||
cursor: default
|
||||
|
||||
a
|
||||
border: 1px solid #281515
|
||||
cursor: pointer
|
||||
font-weight: normal
|
||||
padding: 5px 8px
|
||||
|
||||
&.active
|
||||
background-color: rgba(255, 65, 65, 0.74)
|
||||
cursor: default
|
||||
|
||||
&.disabled
|
||||
background-color: grey
|
||||
cursor: not-allowed
|
||||
|
||||
&#error
|
||||
border: none
|
||||
display: block
|
||||
font-weight: lighter
|
||||
font-size: 0.8rem
|
||||
text-align: center
|
||||
padding: 10px 0 5px 0
|
||||
|
||||
hr
|
||||
margin: 3px 0
|
||||
|
||||
|
||||
#all
|
||||
display: flex
|
||||
justify-content: center
|
||||
margin: 10px 0
|
||||
|
||||
|
||||
.low-reliability
|
||||
text-decoration: underline
|
||||
text-decoration-color: rgb(255, 0, 0)
|
||||
|
||||
.normal-reliability
|
||||
text-decoration: underline
|
||||
text-decoration-color: yellow
|
||||
|
||||
.high-reliability
|
||||
text-decoration: underline
|
||||
text-decoration-color: #00ff00
|
66
src/ui/popup/popup.scss
Normal file
66
src/ui/popup/popup.scss
Normal file
@ -0,0 +1,66 @@
|
||||
body {
|
||||
background-color: #2b2a33;
|
||||
font-weight: bold;
|
||||
max-height: 500px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
a, p {
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
margin: 5px 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
a {
|
||||
border: 1px solid #281515;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
padding: 5px 8px;
|
||||
|
||||
&.active {
|
||||
background-color: rgba(255, 65, 65, 0.74);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background-color: grey;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&#error {
|
||||
border: none;
|
||||
display: block;
|
||||
font-weight: lighter;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
padding: 10px 0 5px 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
#all {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 10px 0
|
||||
}
|
||||
|
||||
.low-reliability {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: rgb(255, 0, 0);
|
||||
}
|
||||
|
||||
.normal-reliability {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: yellow;
|
||||
}
|
||||
|
||||
.high-reliability {
|
||||
text-decoration: underline;
|
||||
text-decoration-color: #00ff00;
|
||||
}
|
@ -47,8 +47,8 @@ async function buildHtml() {
|
||||
|
||||
async function buildCss() {
|
||||
const files = {
|
||||
'src/ui/popup/popup.sass': 'build/ui/popup/popup.css',
|
||||
'src/ui/player/player.sass': 'build/ui/player/player.css'
|
||||
'src/ui/popup/popup.scss': 'build/ui/popup/popup.css',
|
||||
'src/ui/player/player.scss': 'build/ui/player/player.css'
|
||||
}
|
||||
|
||||
for (const [src, dst] of Object.entries(files)) {
|
||||
|
Reference in New Issue
Block a user