Creds sending done

This commit is contained in:
Fergal Moran
2022-04-11 23:03:11 +01:00
parent 81991a22b6
commit 195b48082c
22 changed files with 559 additions and 194 deletions

View File

@@ -1,11 +1,10 @@
import logging import logging
import uvicorn import uvicorn
from fastapi import FastAPI, Request from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from server import config
from server.lib.streamer import Streamer from server.lib.streamer import Streamer
from server.lib.xtream import XTream from server.lib.xtream import XTream
@@ -21,8 +20,6 @@ origins = [
"https://streams.fergl.ie", "https://streams.fergl.ie",
"http://127.0.0.1:35729", "http://127.0.0.1:35729",
"http://localhost:35729", "http://localhost:35729",
"https://bitmovin.com",
"https://players.akamai.com",
] ]
app.add_middleware( app.add_middleware(
@@ -47,6 +44,22 @@ def __get_provider(request: Request):
) )
@app.get("/validate")
async def validate_crendentials(request: Request, response: Response):
try:
provider = __get_provider(request)
categories = provider.get_categories().json()
if type(categories) is list:
return {"status": "accepted"}
except ValueError as e:
logger.error(e)
except Exception as e:
logger.error(e)
response.status_code = 401
return {"status": "denied"}
@app.get("/channels") @app.get("/channels")
async def channels(request: Request): async def channels(request: Request):
provider = __get_provider(request) provider = __get_provider(request)

View File

@@ -18,6 +18,9 @@ class XTream:
_cache = Cache() _cache = Cache()
def __init__(self, server, username, password): def __init__(self, server, username, password):
if not (server and username and password):
raise ValueError("XTream: must specify server, username and password")
self._server = server self._server = server
self._username = username self._username = username
self._password = password self._password = password

View File

@@ -16,6 +16,7 @@
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-focus-lock": "^2.8.1", "react-focus-lock": "^2.8.1",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-hook-form": "^7.29.0",
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"react-router-dom": "6", "react-router-dom": "6",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="700pt" height="700pt" version="1.1" viewBox="0 0 700 700" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<symbol id="w" overflow="visible">
<path d="m18.766-1.125c-0.96875 0.5-1.9805 0.875-3.0312 1.125-1.043 0.25781-2.1367 0.39062-3.2812 0.39062-3.3984 0-6.0898-0.94531-8.0781-2.8438-1.9922-1.9062-2.9844-4.4844-2.9844-7.7344 0-3.2578 0.99219-5.8359 2.9844-7.7344 1.9883-1.9062 4.6797-2.8594 8.0781-2.8594 1.1445 0 2.2383 0.13281 3.2812 0.39062 1.0508 0.25 2.0625 0.625 3.0312 1.125v4.2188c-0.98047-0.65625-1.9453-1.1406-2.8906-1.4531-0.94922-0.3125-1.9492-0.46875-3-0.46875-1.875 0-3.3516 0.60547-4.4219 1.8125-1.0742 1.1992-1.6094 2.8555-1.6094 4.9688 0 2.1055 0.53516 3.7617 1.6094 4.9688 1.0703 1.1992 2.5469 1.7969 4.4219 1.7969 1.0508 0 2.0508-0.14844 3-0.45312 0.94531-0.3125 1.9102-0.80078 2.8906-1.4688z"/>
</symbol>
<symbol id="c" overflow="visible">
<path d="m13.734-11.141c-0.4375-0.19531-0.87109-0.34375-1.2969-0.4375-0.41797-0.10156-0.83984-0.15625-1.2656-0.15625-1.2617 0-2.2305 0.40625-2.9062 1.2188-0.67969 0.80469-1.0156 1.9531-1.0156 3.4531v7.0625h-4.8906v-15.312h4.8906v2.5156c0.625-1 1.3438-1.7266 2.1562-2.1875 0.82031-0.46875 1.8008-0.70312 2.9375-0.70312 0.16406 0 0.34375 0.011719 0.53125 0.03125 0.19531 0.011719 0.47656 0.039062 0.84375 0.078125z"/>
</symbol>
<symbol id="a" overflow="visible">
<path d="m17.641-7.7031v1.4062h-11.453c0.125 1.1484 0.53906 2.0078 1.25 2.5781 0.70703 0.57422 1.7031 0.85938 2.9844 0.85938 1.0312 0 2.082-0.14844 3.1562-0.45312 1.082-0.3125 2.1914-0.77344 3.3281-1.3906v3.7656c-1.1562 0.4375-2.3125 0.76562-3.4688 0.98438-1.1562 0.22656-2.3125 0.34375-3.4688 0.34375-2.7734 0-4.9297-0.70312-6.4688-2.1094-1.5312-1.4062-2.2969-3.3789-2.2969-5.9219 0-2.5 0.75391-4.4609 2.2656-5.8906 1.5078-1.4375 3.582-2.1562 6.2188-2.1562 2.4062 0 4.332 0.73047 5.7812 2.1875 1.4453 1.4492 2.1719 3.3828 2.1719 5.7969zm-5.0312-1.625c0-0.92578-0.27344-1.6719-0.8125-2.2344-0.54297-0.57031-1.25-0.85938-2.125-0.85938-0.94922 0-1.7188 0.26562-2.3125 0.79688s-0.96484 1.2969-1.1094 2.2969z"/>
</symbol>
<symbol id="k" overflow="visible">
<path d="m9.2188-6.8906c-1.0234 0-1.793 0.17188-2.3125 0.51562-0.51172 0.34375-0.76562 0.85547-0.76562 1.5312 0 0.625 0.20703 1.1172 0.625 1.4688 0.41406 0.34375 0.98828 0.51562 1.7188 0.51562 0.92578 0 1.7031-0.32812 2.3281-0.98438 0.63281-0.66406 0.95312-1.4922 0.95312-2.4844v-0.5625zm7.4688-1.8438v8.7344h-4.9219v-2.2656c-0.65625 0.92969-1.3984 1.6055-2.2188 2.0312-0.82422 0.41406-1.8242 0.625-3 0.625-1.5859 0-2.8711-0.45703-3.8594-1.375-0.99219-0.92578-1.4844-2.1289-1.4844-3.6094 0-1.7891 0.61328-3.1016 1.8438-3.9375 1.2383-0.84375 3.1797-1.2656 5.8281-1.2656h2.8906v-0.39062c0-0.76953-0.30859-1.332-0.92188-1.6875-0.61719-0.36328-1.5703-0.54688-2.8594-0.54688-1.0547 0-2.0312 0.10547-2.9375 0.3125-0.89844 0.21094-1.7305 0.52344-2.5 0.9375v-3.7344c1.0391-0.25 2.0859-0.44141 3.1406-0.57812 1.0625-0.13281 2.125-0.20312 3.1875-0.20312 2.7578 0 4.75 0.54688 5.9688 1.6406 1.2266 1.0859 1.8438 2.8555 1.8438 5.3125z"/>
</symbol>
<symbol id="b" overflow="visible">
<path d="m7.7031-19.656v4.3438h5.0469v3.5h-5.0469v6.5c0 0.71094 0.14062 1.1875 0.42188 1.4375s0.83594 0.375 1.6719 0.375h2.5156v3.5h-4.1875c-1.9375 0-3.3125-0.39844-4.125-1.2031-0.80469-0.8125-1.2031-2.1797-1.2031-4.1094v-6.5h-2.4219v-3.5h2.4219v-4.3438z"/>
</symbol>
<symbol id="j" overflow="visible">
<path d="m12.766-13.078v-8.2031h4.9219v21.281h-4.9219v-2.2188c-0.66797 0.90625-1.4062 1.5703-2.2188 1.9844s-1.7578 0.625-2.8281 0.625c-1.8867 0-3.4336-0.75-4.6406-2.25-1.2109-1.5-1.8125-3.4258-1.8125-5.7812 0-2.3633 0.60156-4.2969 1.8125-5.7969 1.207-1.5 2.7539-2.25 4.6406-2.25 1.0625 0 2 0.21484 2.8125 0.64062 0.82031 0.42969 1.5664 1.0859 2.2344 1.9688zm-3.2188 9.9219c1.0391 0 1.8359-0.37891 2.3906-1.1406 0.55078-0.76953 0.82812-1.8828 0.82812-3.3438 0-1.457-0.27734-2.5664-0.82812-3.3281-0.55469-0.76953-1.3516-1.1562-2.3906-1.1562-1.043 0-1.8398 0.38672-2.3906 1.1562-0.55469 0.76172-0.82812 1.8711-0.82812 3.3281 0 1.4609 0.27344 2.5742 0.82812 3.3438 0.55078 0.76172 1.3477 1.1406 2.3906 1.1406z"/>
</symbol>
<symbol id="i" overflow="visible">
<path d="m10.5-3.1562c1.0508 0 1.8516-0.37891 2.4062-1.1406 0.55078-0.76953 0.82812-1.8828 0.82812-3.3438 0-1.457-0.27734-2.5664-0.82812-3.3281-0.55469-0.76953-1.3555-1.1562-2.4062-1.1562-1.0547 0-1.8594 0.38672-2.4219 1.1562-0.55469 0.77344-0.82812 1.8828-0.82812 3.3281 0 1.4492 0.27344 2.5586 0.82812 3.3281 0.5625 0.77344 1.3672 1.1562 2.4219 1.1562zm-3.25-9.9219c0.67578-0.88281 1.4219-1.5391 2.2344-1.9688 0.82031-0.42578 1.7656-0.64062 2.8281-0.64062 1.8945 0 3.4453 0.75 4.6562 2.25 1.207 1.5 1.8125 3.4336 1.8125 5.7969 0 2.3555-0.60547 4.2812-1.8125 5.7812-1.2109 1.5-2.7617 2.25-4.6562 2.25-1.0625 0-2.0078-0.21094-2.8281-0.625-0.8125-0.42578-1.5586-1.0859-2.2344-1.9844v2.2188h-4.8906v-21.281h4.8906z"/>
</symbol>
<symbol id="h" overflow="visible">
<path d="m0.34375-15.312h4.8906l4.125 10.391 3.5-10.391h4.8906l-6.4375 16.766c-0.64844 1.6953-1.4023 2.8828-2.2656 3.5625-0.86719 0.6875-2 1.0312-3.4062 1.0312h-2.8438v-3.2188h1.5312c0.83203 0 1.4375-0.13672 1.8125-0.40625 0.38281-0.26172 0.67969-0.73047 0.89062-1.4062l0.14062-0.42188z"/>
</symbol>
<symbol id="e" overflow="visible">
<path d="m2.5781-20.406h5.875l7.4219 14v-14h4.9844v20.406h-5.875l-7.4219-14v14h-4.9844z"/>
</symbol>
<symbol id="g" overflow="visible">
<path d="m2.3594-15.312h4.8906v15.312h-4.8906zm0-5.9688h4.8906v4h-4.8906z"/>
</symbol>
<symbol id="v" overflow="visible">
<path d="m2.3594-21.281h4.8906v11.594l5.625-5.625h5.6875l-7.4688 7.0312 8.0625 8.2812h-5.9375l-5.9688-6.3906v6.3906h-4.8906z"/>
</symbol>
<symbol id="f" overflow="visible">
<path d="m17.75-9.3281v9.3281h-4.9219v-7.1094c0-1.3438-0.03125-2.2656-0.09375-2.7656s-0.16797-0.86719-0.3125-1.1094c-0.1875-0.3125-0.44922-0.55469-0.78125-0.73438-0.32422-0.17578-0.69531-0.26562-1.1094-0.26562-1.0234 0-1.8242 0.39844-2.4062 1.1875-0.58594 0.78125-0.875 1.8711-0.875 3.2656v7.5312h-4.8906v-21.281h4.8906v8.2031c0.73828-0.88281 1.5195-1.5391 2.3438-1.9688 0.83203-0.42578 1.75-0.64062 2.75-0.64062 1.7695 0 3.1133 0.54688 4.0312 1.6406 0.91406 1.0859 1.375 2.6562 1.375 4.7188z"/>
</symbol>
<symbol id="u" overflow="visible">
<path d="m2.3594-21.281h4.8906v21.281h-4.8906z"/>
</symbol>
<symbol id="t" overflow="visible">
<path d="m7.8281-16.438v12.453h1.8906c2.1562 0 3.8008-0.53125 4.9375-1.5938 1.1328-1.0625 1.7031-2.6133 1.7031-4.6562 0-2.0195-0.57031-3.5547-1.7031-4.6094-1.125-1.0625-2.7734-1.5938-4.9375-1.5938zm-5.25-3.9688h5.5469c3.0938 0 5.3984 0.21875 6.9219 0.65625 1.5195 0.4375 2.8203 1.1875 3.9062 2.25 0.95703 0.91797 1.6641 1.9805 2.125 3.1875 0.46875 1.1992 0.70312 2.5586 0.70312 4.0781 0 1.543-0.23438 2.918-0.70312 4.125-0.46094 1.2109-1.168 2.2773-2.125 3.2031-1.0938 1.0547-2.4062 1.8047-3.9375 2.25-1.5312 0.4375-3.8281 0.65625-6.8906 0.65625h-5.5469z"/>
</symbol>
<symbol id="s" overflow="visible">
<path d="m0.42188-15.312h4.8906l3.8281 10.578 3.7969-10.578h4.9062l-6.0312 15.312h-5.375z"/>
</symbol>
<symbol id="r" overflow="visible">
<path d="m12.422-21.281v3.2188h-2.7031c-0.6875 0-1.1719 0.125-1.4531 0.375-0.27344 0.25-0.40625 0.6875-0.40625 1.3125v1.0625h4.1875v3.5h-4.1875v11.812h-4.8906v-11.812h-2.4375v-3.5h2.4375v-1.0625c0-1.6641 0.46094-2.8984 1.3906-3.7031 0.92578-0.80078 2.3672-1.2031 4.3281-1.2031z"/>
</symbol>
<symbol id="d" overflow="visible">
<path d="m9.6406-12.188c-1.0859 0-1.9141 0.39062-2.4844 1.1719-0.57422 0.78125-0.85938 1.9062-0.85938 3.375s0.28516 2.5938 0.85938 3.375c0.57031 0.77344 1.3984 1.1562 2.4844 1.1562 1.0625 0 1.875-0.38281 2.4375-1.1562 0.57031-0.78125 0.85938-1.9062 0.85938-3.375s-0.28906-2.5938-0.85938-3.375c-0.5625-0.78125-1.375-1.1719-2.4375-1.1719zm0-3.5c2.6328 0 4.6914 0.71484 6.1719 2.1406 1.4766 1.418 2.2188 3.3867 2.2188 5.9062 0 2.5117-0.74219 4.4805-2.2188 5.9062-1.4805 1.418-3.5391 2.125-6.1719 2.125-2.6484 0-4.7148-0.70703-6.2031-2.125-1.4922-1.4258-2.2344-3.3945-2.2344-5.9062 0-2.5195 0.74219-4.4883 2.2344-5.9062 1.4883-1.4258 3.5547-2.1406 6.2031-2.1406z"/>
</symbol>
<symbol id="q" overflow="visible">
<path d="m16.547-12.766c0.61328-0.94531 1.3477-1.6719 2.2031-2.1719 0.85156-0.5 1.7891-0.75 2.8125-0.75 1.7578 0 3.0977 0.54688 4.0156 1.6406 0.92578 1.0859 1.3906 2.6562 1.3906 4.7188v9.3281h-4.9219v-7.9844-0.35938c0.007813-0.13281 0.015625-0.32031 0.015625-0.5625 0-1.082-0.16406-1.8633-0.48438-2.3438-0.3125-0.48828-0.82422-0.73438-1.5312-0.73438-0.92969 0-1.6484 0.38672-2.1562 1.1562-0.51172 0.76172-0.77344 1.8672-0.78125 3.3125v7.5156h-4.9219v-7.9844c0-1.6953-0.14844-2.7852-0.4375-3.2656-0.29297-0.48828-0.8125-0.73438-1.5625-0.73438-0.9375 0-1.6641 0.38672-2.1719 1.1562-0.51172 0.76172-0.76562 1.8594-0.76562 3.2969v7.5312h-4.9219v-15.312h4.9219v2.2344c0.60156-0.86328 1.2891-1.5156 2.0625-1.9531 0.78125-0.4375 1.6406-0.65625 2.5781-0.65625 1.0625 0 2 0.25781 2.8125 0.76562 0.8125 0.51172 1.4258 1.2305 1.8438 2.1562z"/>
</symbol>
<symbol id="p" overflow="visible">
<path d="m2.1875-5.9688v-9.3438h4.9219v1.5312c0 0.83594-0.007813 1.875-0.015625 3.125-0.011719 1.25-0.015625 2.0859-0.015625 2.5 0 1.2422 0.03125 2.1328 0.09375 2.6719 0.070313 0.54297 0.17969 0.93359 0.32812 1.1719 0.20703 0.32422 0.47266 0.57422 0.79688 0.75 0.32031 0.16797 0.69141 0.25 1.1094 0.25 1.0195 0 1.8203-0.39062 2.4062-1.1719 0.58203-0.78125 0.875-1.8672 0.875-3.2656v-7.5625h4.8906v15.312h-4.8906v-2.2188c-0.74219 0.89844-1.5234 1.5586-2.3438 1.9844-0.82422 0.41406-1.7344 0.625-2.7344 0.625-1.7617 0-3.1055-0.53906-4.0312-1.625-0.92969-1.082-1.3906-2.6602-1.3906-4.7344z"/>
</symbol>
<symbol id="o" overflow="visible">
<path d="m17.75-9.3281v9.3281h-4.9219v-7.1406c0-1.3203-0.03125-2.2344-0.09375-2.7344s-0.16797-0.86719-0.3125-1.1094c-0.1875-0.3125-0.44922-0.55469-0.78125-0.73438-0.32422-0.17578-0.69531-0.26562-1.1094-0.26562-1.0234 0-1.8242 0.39844-2.4062 1.1875-0.58594 0.78125-0.875 1.8711-0.875 3.2656v7.5312h-4.8906v-15.312h4.8906v2.2344c0.73828-0.88281 1.5195-1.5391 2.3438-1.9688 0.83203-0.42578 1.75-0.64062 2.75-0.64062 1.7695 0 3.1133 0.54688 4.0312 1.6406 0.91406 1.0859 1.375 2.6562 1.375 4.7188z"/>
</symbol>
<symbol id="n" overflow="visible">
<path d="m2.5781-20.406h8.7344c2.5938 0 4.582 0.57812 5.9688 1.7344 1.3945 1.1484 2.0938 2.7891 2.0938 4.9219 0 2.1367-0.69922 3.7812-2.0938 4.9375-1.3867 1.1562-3.375 1.7344-5.9688 1.7344h-3.4844v7.0781h-5.25zm5.25 3.8125v5.7031h2.9219c1.0195 0 1.8047-0.25 2.3594-0.75 0.5625-0.5 0.84375-1.2031 0.84375-2.1094 0-0.91406-0.28125-1.6172-0.84375-2.1094-0.55469-0.48828-1.3398-0.73438-2.3594-0.73438z"/>
</symbol>
<symbol id="m" overflow="visible">
<path d="m2.3594-15.312h4.8906v15.031c0 2.0508-0.49609 3.6172-1.4844 4.7031-0.98047 1.082-2.4062 1.625-4.2812 1.625h-2.4219v-3.2188h0.85938c0.92578 0 1.5625-0.21094 1.9062-0.625 0.35156-0.41797 0.53125-1.2461 0.53125-2.4844zm0-5.9688h4.8906v4h-4.8906z"/>
</symbol>
<symbol id="l" overflow="visible">
<path d="m14.719-14.828v3.9844c-0.65625-0.45703-1.3242-0.79688-2-1.0156-0.66797-0.21875-1.3594-0.32812-2.0781-0.32812-1.3672 0-2.4336 0.40234-3.2031 1.2031-0.76172 0.79297-1.1406 1.9062-1.1406 3.3438 0 1.4297 0.37891 2.543 1.1406 3.3438 0.76953 0.79297 1.8359 1.1875 3.2031 1.1875 0.75781 0 1.4844-0.10938 2.1719-0.32812 0.6875-0.22656 1.3203-0.56641 1.9062-1.0156v4c-0.76172 0.28125-1.5391 0.48828-2.3281 0.625-0.78125 0.14453-1.5742 0.21875-2.375 0.21875-2.7617 0-4.9219-0.70703-6.4844-2.125-1.5547-1.4141-2.3281-3.3828-2.3281-5.9062 0-2.5312 0.77344-4.5039 2.3281-5.9219 1.5625-1.4141 3.7227-2.125 6.4844-2.125 0.80078 0 1.5938 0.074219 2.375 0.21875 0.78125 0.13672 1.5547 0.35156 2.3281 0.64062z"/>
</symbol>
</defs>
<g>
<path d="m-1005.2 0h1036v554.4h-1036z"/>
<path d="m588.68 103.72h-477.37c-18.531 0-33.59 18.039-33.59 40.27v272.02c0 22.266 15.059 40.27 33.59 40.27h477.37c18.535 0 33.594-18.008 33.594-40.27v-272.01c0-22.238-15.059-40.273-33.594-40.273zm-104.38 278.76c0 16.762-11.328 30.32-25.277 30.32l-311.24-0.003906c-13.945 0-25.277-13.559-25.277-30.32v-204.93c0-16.762 11.328-30.352 25.277-30.352h311.24c13.945 0 25.277 13.59 25.277 30.352v204.93zm69.148-51.992c-23.375 0-42.363-18.996-42.363-42.438 0-23.375 18.996-42.363 42.363-42.363 23.445 0 42.363 18.996 42.363 42.363 0 23.445-18.914 42.438-42.363 42.438z" fill-rule="evenodd"/>
<path d="m241.23 188.95 157.68 91.055-157.68 91.086z"/>
<use x="70" y="644" xlink:href="#w"/>
<use x="90.550781" y="644" xlink:href="#c"/>
<use x="104.359375" y="644" xlink:href="#a"/>
<use x="123.347656" y="644" xlink:href="#k"/>
<use x="142.242188" y="644" xlink:href="#b"/>
<use x="155.628906" y="644" xlink:href="#a"/>
<use x="174.617188" y="644" xlink:href="#j"/>
<use x="204.410156" y="644" xlink:href="#i"/>
<use x="224.453125" y="644" xlink:href="#h"/>
<use x="252.453125" y="644" xlink:href="#e"/>
<use x="275.886719" y="644" xlink:href="#g"/>
<use x="285.484375" y="644" xlink:href="#v"/>
<use x="304.105469" y="644" xlink:href="#f"/>
<use x="324.039062" y="644" xlink:href="#g"/>
<use x="333.632812" y="644" xlink:href="#u"/>
<use x="352.980469" y="644" xlink:href="#t"/>
<use x="376.222656" y="644" xlink:href="#a"/>
<use x="395.210938" y="644" xlink:href="#s"/>
<use x="70" y="672" xlink:href="#r"/>
<use x="82.183594" y="672" xlink:href="#c"/>
<use x="95.992188" y="672" xlink:href="#d"/>
<use x="115.226562" y="672" xlink:href="#q"/>
<use x="154.152344" y="672" xlink:href="#b"/>
<use x="167.535156" y="672" xlink:href="#f"/>
<use x="187.46875" y="672" xlink:href="#a"/>
<use x="216.207031" y="672" xlink:href="#e"/>
<use x="239.640625" y="672" xlink:href="#d"/>
<use x="258.878906" y="672" xlink:href="#p"/>
<use x="278.8125" y="672" xlink:href="#o"/>
<use x="308.492188" y="672" xlink:href="#n"/>
<use x="329.015625" y="672" xlink:href="#c"/>
<use x="342.820312" y="672" xlink:href="#d"/>
<use x="362.058594" y="672" xlink:href="#m"/>
<use x="371.65625" y="672" xlink:href="#a"/>
<use x="390.648438" y="672" xlink:href="#l"/>
<use x="407.242188" y="672" xlink:href="#b"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -8,9 +8,12 @@ function App() {
<BrowserRouter> <BrowserRouter>
<Routes> <Routes>
{localStorage.getItem("server") ? ( {localStorage.getItem("server") ? (
<Route path="/*" element={<OnboardingPage />} /> <>
) : ( <Route path="/onboarding" element={<OnboardingPage />} />
<Route path="/*" element={<Layout />} /> <Route path="/*" element={<Layout />} />
</>
) : (
<Route path="/*" element={<OnboardingPage />} />
)} )}
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -3,5 +3,13 @@ import Navbar from "./navbar.component";
import Sidebar from "./sidebar/sidebar-content.component"; import Sidebar from "./sidebar/sidebar-content.component";
import EPGComponent from "./epg.component"; import EPGComponent from "./epg.component";
import ThemedSuspence from "./themed-suspence.component"; import ThemedSuspence from "./themed-suspence.component";
import ServerDetails from "./server-details.component";
export { Navbar, Sidebar, HLSPlayer, EPGComponent, ThemedSuspence }; export {
Navbar,
Sidebar,
HLSPlayer,
EPGComponent,
ThemedSuspence,
ServerDetails,
};

View File

@@ -0,0 +1,107 @@
import React from "react";
import { Input, Label, Button, HelperText } from "./widgets";
import { useForm } from "react-hook-form";
import { BiRocket } from "react-icons/bi";
import { ApiService } from "../services";
import { toast } from "react-toastify";
const ServerDetails = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = async (data: any) => {
console.log("server-details.component", "onSubmit", errors);
const validated = await ApiService.validateCredentials(
data.server,
data.username,
data.password
);
if (validated) {
localStorage.setItem("server", JSON.stringify(data));
window.location.reload();
} else {
toast.error("FUCK YOU INVALID CREDENTIALS");
}
};
return (
<div className="w-full">
<h1 className="mb-4 text-xl font-semibold text-gray-700 dark:text-gray-200">
XTream Codes Details
</h1>
<form onSubmit={handleSubmit(onSubmit)} autoComplete="false">
<input
autoComplete="false"
name="hidden"
type="text"
style={{ display: "none" }}
/>
<Label>
<span>Server address</span>
<Input
className="mt-1"
type="text"
placeholder="my.streams.com"
autoComplete="off"
{...register("server", {
required: "Server is required",
})}
valid={!errors.server}
/>
{errors.server && (
<HelperText valid={false}>{errors.server.message}</HelperText>
)}
</Label>
<Label className="mt-4">
<span>Username</span>
<Input
className="mt-1"
type="text"
placeholder="username"
autoComplete="off"
data-lpignore="true"
{...register("username", {
required: "Username is required",
})}
valid={!errors.username}
/>
{errors.username && (
<HelperText valid={false}>{errors.username.message}</HelperText>
)}{" "}
</Label>
<Label className="mt-4">
<span>Password</span>
<Input
className="mt-1"
type="password"
autoComplete="off"
data-lpignore="true"
placeholder="***************"
{...register("password", {
required: "Password is required",
})}
valid={!errors.password}
/>
{errors.password && (
<HelperText valid={false}>{errors.password.message}</HelperText>
)}
</Label>
<Button
className="mt-4"
block
type="submit"
aria-label="Submit"
icon={BiRocket}
>
Let's go!
</Button>
<hr className="my-8" />
</form>
</div>
);
};
export default ServerDetails;

View File

@@ -1,14 +1,25 @@
import { Transition } from "@headlessui/react";
import React from "react"; import React from "react";
import { SidebarContext } from "../../context"; import { SidebarContext } from "../../context";
import SidebarContent from "./sidebar-content.component"; import SidebarContent from "./sidebar-content.component";
const MobileSidebar = () => { const MobileSidebar = () => {
const { isSidebarOpen } = React.useContext(SidebarContext); const { isSidebarOpen } = React.useContext(SidebarContext);
return isSidebarOpen ? ( return (
<Transition
show={isSidebarOpen}
enter="transition-opacity duration-75"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<aside className="fixed inset-y-0 z-50 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 lg:hidden"> <aside className="fixed inset-y-0 z-50 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 lg:hidden">
<SidebarContent /> <SidebarContent />
</aside> </aside>
) : null; </Transition>
);
}; };
export default MobileSidebar; export default MobileSidebar;

View File

@@ -1,44 +1,44 @@
import React from "react"; import React from "react";
import { NavLink, Route } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { Channel } from "../../models/channel"; import { Channel } from "../../models/channel";
import { ApiService } from "../../services"; import { ApiService } from "../../services";
const SidebarContent = () => { const SidebarContent = () => {
const [channels, setChannels] = React.useState<Channel[]>([]); const [channels, setChannels] = React.useState<Channel[]>([]);
const [filteredChannels, setFilteredChannels] = React.useState<Channel[]>([]); // const [filteredChannels, setFilteredChannels] = React.useState<Channel[]>([]);
React.useEffect(() => { React.useEffect(() => {
const fetchChannels = async () => { const fetchChannels = async () => {
const res = await ApiService.getChannels(); const res = await ApiService.getChannels();
if (res) { if (res) {
setChannels(res); setChannels(res);
setFilteredChannels(res);
} }
}; };
fetchChannels().catch(console.error); fetchChannels();
}, []); }, []);
const _searchChannels = ($event: React.ChangeEvent<HTMLInputElement>) => { // const _searchChannels = ($event: React.ChangeEvent<HTMLInputElement>) => {
const searchString = $event.target.value; // const searchString = $event.target.value;
if (searchString) { // if (searchString) {
const filteredChannels = channels.filter((c) => { // const filteredChannels = channels.filter((c) => {
const result = c.category_name // const result = c.category_name
.toLowerCase() // .toLowerCase()
.includes(searchString.toLowerCase()); // .includes(searchString.toLowerCase());
console.log( // console.log(
"sidebar.component", // "sidebar.component",
`Category Name: ${c.category_name}`, // `Category Name: ${c.category_name}`,
`Search String: ${searchString}` // `Search String: ${searchString}`
); // );
console.log("sidebar.component", "Result", result); // console.log("sidebar.component", "Result", result);
return result; // return result;
}); // });
setFilteredChannels(filteredChannels); // setFilteredChannels(filteredChannels);
} else { // } else {
setFilteredChannels(channels); // setFilteredChannels(channels);
} // }
}; // };
return ( return (
<div className="py-4 text-gray-500 dark:text-gray-400"> channels && (
<div className="py-4 text-gray-500 dark:text-gray-400 scroller">
<a <a
className="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200" className="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200"
href="/" href="/"
@@ -46,7 +46,7 @@ const SidebarContent = () => {
Xtreamium Xtreamium
</a> </a>
<ul className="mt-6"> <ul className="mt-6">
{filteredChannels.map((channel: Channel) => ( {channels.map((channel: Channel) => (
<li className="relative px-6 py-3" key={channel.category_id}> <li className="relative px-6 py-3" key={channel.category_id}>
<NavLink <NavLink
to={`/live/channel/${channel.category_id}`} to={`/live/channel/${channel.category_id}`}
@@ -74,6 +74,7 @@ const SidebarContent = () => {
))} ))}
</ul> </ul>
</div> </div>
)
); );
}; };

View File

@@ -65,12 +65,12 @@ const Button = React.forwardRef<Ref, ButtonProps>(function Button(props, ref) {
return !!icon || !!iconLeft || !!iconRight; return !!icon || !!iconLeft || !!iconRight;
} }
if (hasIcon() && !other["aria-label"] && !children) {
console.warn( console.warn(
hasIcon() && !other["aria-label"] && !children,
"Button", "Button",
'You are using an icon button, but no "aria-label" attribute was found. Add an "aria-label" attribute to work as a label for screen readers.' 'You are using an icon button, but no "aria-label" attribute was found. Add an "aria-label" attribute to work as a label for screen readers.'
); );
}
const IconLeft = iconLeft || icon; const IconLeft = iconLeft || icon;
const IconRight = iconRight; const IconRight = iconRight;

View File

@@ -0,0 +1,41 @@
import React from "react";
import classNames from "classnames";
import { defaultTheme } from "../../constants";
export interface HelperTextProps extends React.HTMLAttributes<HTMLSpanElement> {
/**
* Defines the color of the helper text (the same as with Input, Select, etc.)
*/
valid?: boolean;
}
const HelperText = React.forwardRef<HTMLSpanElement, HelperTextProps>(
function HelperText(props, ref) {
const { children, valid, className, ...other } = props;
const { helperText } = defaultTheme;
const baseStyle = helperText.base;
const validStyle = helperText.valid;
const invalidStyle = helperText.invalid;
const validationStyle = (valid: boolean | undefined): string => {
switch (valid) {
case true:
return validStyle;
case false:
return invalidStyle;
default:
return "";
}
};
const cls = classNames(baseStyle, validationStyle(valid), className);
return (
<span className={cls} ref={ref} {...other}>
{children}
</span>
);
}
);
export default HelperText;

View File

@@ -0,0 +1,18 @@
import React, { SyntheticEvent } from "react";
interface ImageWithFallbackProps
extends React.ImgHTMLAttributes<HTMLImageElement> {
src: string;
fallback: string;
}
const ImageWithFallback = ({
fallback,
src,
...props
}: ImageWithFallbackProps) => {
return (
<img {...props} src={src} onError={(e: any) => (e.target.src = fallback)} />
);
};
export default ImageWithFallback;

View File

@@ -2,7 +2,19 @@ import Avatar from "./avatar.component";
import Badge from "./badge.component"; import Badge from "./badge.component";
import Button from "./button.component"; import Button from "./button.component";
import { Dropdown, DropdownItem } from "./dropdown.component"; import { Dropdown, DropdownItem } from "./dropdown.component";
import HelperText from "./helper-text.component";
import ImageWithFallback from "./image-fallback.component";
import Input from "./input.component"; import Input from "./input.component";
import Label from "./label.component"; import Label from "./label.component";
export { Avatar, Badge, Button, Input, Dropdown, DropdownItem, Label }; export {
Avatar,
Badge,
Button,
Input,
Dropdown,
DropdownItem,
Label,
HelperText,
ImageWithFallback,
};

View File

@@ -6,13 +6,23 @@ export interface InputProps extends React.ComponentPropsWithRef<"input"> {
valid?: boolean; valid?: boolean;
disabled?: boolean; disabled?: boolean;
type?: string; type?: string;
formControlName?: string;
register?: any;
} }
const Input = React.forwardRef<HTMLInputElement, InputProps>(function Input( const Input = React.forwardRef<HTMLInputElement, InputProps>(function Input(
props, props,
ref ref
) { ) {
const { valid, disabled, className, type = "text", ...other } = props; const {
valid,
disabled,
className,
type = "text",
formControlName,
register,
...other
} = props;
const { input } = defaultTheme; const { input } = defaultTheme;
const baseStyle = input.base; const baseStyle = input.base;

View File

@@ -2,7 +2,7 @@ import React, { Suspense } from "react";
import { useLocation, Routes, Route } from "react-router-dom"; import { useLocation, Routes, Route } from "react-router-dom";
import Header from "../components/header.component"; import Header from "../components/header.component";
import Main from "./main.container"; import Main from "./main.container";
import { ChannelPage, PlayerPage } from "../pages"; import { ChannelPage, HomePage, PlayerPage } from "../pages";
import ThemedSuspence from "../components/themed-suspence.component"; import ThemedSuspence from "../components/themed-suspence.component";
import { SidebarContext } from "../context"; import { SidebarContext } from "../context";
import Sidebar from "../components/sidebar"; import Sidebar from "../components/sidebar";
@@ -28,6 +28,7 @@ const Layout = () => {
<Routes> <Routes>
<Route path="live/channel/:channelId" element={<ChannelPage />} /> <Route path="live/channel/:channelId" element={<ChannelPage />} />
<Route path="live/play/:streamId" element={<PlayerPage />} /> <Route path="live/play/:streamId" element={<PlayerPage />} />
<Route path="*" element={<HomePage />} />
</Routes> </Routes>
</Suspense> </Suspense>
</Main> </Main>

View File

@@ -5,7 +5,7 @@ interface IMainProps {
} }
const Main = ({ children }: IMainProps) => { const Main = ({ children }: IMainProps) => {
return ( return (
<main className="h-full overflow-y-auto"> <main className="h-screen pb-16 overflow-y-hidden">
<div className="container grid px-6 mx-auto">{children}</div> <div className="container grid px-6 mx-auto">{children}</div>
</main> </main>
); );

View File

@@ -3,3 +3,50 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
html {
scrollbar-width: thin;
}
.scroller:hover::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.scroller:hover::-webkit-scrollbar-thumb,
#aside.scroller:hover::-webkit-scrollbar-thumb {
@apply rounded;
}
html {
@apply overflow-auto;
scrollbar-color: #9ca3af #e5e7eb;
}
.scroller:hover::-webkit-scrollbar-track {
@apply bg-gray-200;
}
.scroller:hover::-webkit-scrollbar-thumb {
@apply bg-gray-400;
}
.scroller:hover::-webkit-scrollbar-thumb:hover {
@apply bg-gray-500;
}
html.dark {
scrollbar-color: #374151 #111827;
}
html.dark.scroller:hover::-webkit-scrollbar-track {
@apply bg-gray-900;
}
html.dark.scroller:hover::-webkit-scrollbar-thumb {
@apply bg-gray-700;
}
html.dark.scroller:hover::-webkit-scrollbar-thumb:hover {
@apply bg-gray-600;
}

View File

@@ -4,12 +4,7 @@ import { AiOutlinePlayCircle } from "react-icons/ai";
import { Stream } from "../models/stream"; import { Stream } from "../models/stream";
import { convertEpochToSpecificTimezone } from "../utils/date-utils"; import { convertEpochToSpecificTimezone } from "../utils/date-utils";
import { EPGComponent } from "../components"; import { EPGComponent } from "../components";
import {
CastButton,
CastProvider,
useCast,
useMedia,
} from "../utils/chromecast";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { import {
Table, Table,
@@ -19,16 +14,13 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "../components/widgets/table"; } from "../components/widgets/table";
import { Avatar, Badge, Button } from "../components/widgets"; import { Badge, Button, ImageWithFallback } from "../components/widgets";
import { ApiService } from "../services"; import { ApiService } from "../services";
const ChannelPage = () => { const ChannelPage = () => {
let params = useParams(); let params = useParams();
const cast = useCast();
const media = useMedia();
const [streams, setStreams] = React.useState<Stream[]>([]); const [streams, setStreams] = React.useState<Stream[]>([]);
const [currentVideoUrl, setCurrentVideoUrl] = React.useState("");
React.useEffect(() => { React.useEffect(() => {
const fetchChannels = async () => { const fetchChannels = async () => {
@@ -41,21 +33,6 @@ const ChannelPage = () => {
fetchChannels().catch(console.error); fetchChannels().catch(console.error);
}, [params.channelId]); }, [params.channelId]);
const _cast = React.useCallback(
async (streamId: number) => {
const streamUrl = await ApiService.getStreamUrl(streamId);
if (streamUrl) {
await media.playMedia(streamUrl);
}
},
[media]
);
const handleXHR = (...args: any[]) => {
console.log("channel.page", "handleXHR", args);
};
const playStream = async (streamId: number) => { const playStream = async (streamId: number) => {
const url = await ApiService.getStreamUrl(streamId); const url = await ApiService.getStreamUrl(streamId);
if (url) { if (url) {
@@ -125,7 +102,6 @@ const ChannelPage = () => {
} }
}; };
return ( return (
<CastProvider>
<TableContainer className="mt-5 mb-8"> <TableContainer className="mt-5 mb-8">
<Table> <Table>
<TableHeader> <TableHeader>
@@ -140,10 +116,11 @@ const ChannelPage = () => {
<TableRow key={stream.num}> <TableRow key={stream.num}>
<TableCell> <TableCell>
<div className="flex items-center text-sm"> <div className="flex items-center text-sm">
<Avatar <ImageWithFallback
className="hidden w-10 h-10 ml-2 mr-3 md:block" className="hidden w-10 h-10 ml-2 mr-3 md:block"
src={stream.stream_icon} src={stream.stream_icon}
alt="Stream icon" alt="Stream icon"
fallback="/images/unknown-stream.svg"
/> />
<div> <div>
<p className="font-semibold">{stream.name}</p> <p className="font-semibold">{stream.name}</p>
@@ -164,7 +141,6 @@ const ChannelPage = () => {
aria-label="Edit" aria-label="Edit"
onClick={() => playStream(stream.stream_id)} onClick={() => playStream(stream.stream_id)}
></Button> ></Button>
<CastButton streamId={stream.stream_id} onPlay={_cast} />
</div> </div>
</TableCell> </TableCell>
</TableRow>, </TableRow>,
@@ -181,7 +157,6 @@ const ChannelPage = () => {
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
</CastProvider>
); );
}; };

View File

@@ -1,8 +1,6 @@
import React from "react";
import ImageLight from "../assets/images/love-tv.jpg"; import ImageLight from "../assets/images/love-tv.jpg";
import { Button, Input, Label } from "../components/widgets"; import ServerDetails from "../components/server-details.component";
const OnboardingPage = () => { const OnboardingPage = () => {
const _setupOnboarding = () => {};
return ( return (
<div className="flex items-center min-h-screen p-6 bg-gray-50 dark:bg-gray-900"> <div className="flex items-center min-h-screen p-6 bg-gray-50 dark:bg-gray-900">
<div className="flex-1 h-full max-w-4xl mx-auto overflow-hidden bg-white rounded-lg shadow-xl dark:bg-gray-800"> <div className="flex-1 h-full max-w-4xl mx-auto overflow-hidden bg-white rounded-lg shadow-xl dark:bg-gray-800">
@@ -22,34 +20,7 @@ const OnboardingPage = () => {
/> />
</div> </div>
<main className="flex items-center justify-center p-6 sm:p-12 md:w-1/2"> <main className="flex items-center justify-center p-6 sm:p-12 md:w-1/2">
<div className="w-full"> <ServerDetails />
<h1 className="mb-4 text-xl font-semibold text-gray-700 dark:text-gray-200">
Login
</h1>
<Label>
<span>Email</span>
<Input
className="mt-1"
type="email"
placeholder="john@doe.com"
/>
</Label>
<Label className="mt-4">
<span>Password</span>
<Input
className="mt-1"
type="password"
placeholder="***************"
/>
</Label>
<Button className="mt-4" block onClick={_setupOnboarding}>
Log in
</Button>
<hr className="my-8" />
</div>
</main> </main>
</div> </div>
</div> </div>

View File

@@ -1,8 +1,31 @@
import http from "./http.service"; import http from "./http.service";
import { Channel } from "../models/channel"; import { Channel } from "../models/channel";
import { Stream } from "../models/stream"; import { Stream } from "../models/stream";
import axios from "axios";
class ApiService { class ApiService {
public validateCredentials = async (
server: string,
username: string,
password: string
): Promise<Boolean> => {
const client = axios.create({
baseURL: process.env.REACT_APP_API_URL,
headers: {
"Content-type": "application/json",
"x-xtream-server": server,
"x-xtream-username": username,
"x-xtream-password": password,
},
});
try {
const res = await client.get("/validate");
return res.status === 200;
} catch {
return false;
}
};
public getChannels = async (): Promise<Channel[]> => { public getChannels = async (): Promise<Channel[]> => {
const response = await http.get("/channels"); const response = await http.get("/channels");
return response.data; return response.data;

View File

@@ -7149,6 +7149,11 @@ react-helmet@^6.1.0:
react-fast-compare "^3.1.1" react-fast-compare "^3.1.1"
react-side-effect "^2.1.0" react-side-effect "^2.1.0"
react-hook-form@^7.29.0:
version "7.29.0"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.29.0.tgz#5e7e41a483b70731720966ed8be52163ea1fecf1"
integrity sha512-NcJqWRF6el5HMW30fqZRt27s+lorvlCCDbTpAyHoodQeYWXgQCvZJJQLC1kRMKdrJknVH0NIg3At6TUzlZJFOQ==
react-icons@^4.3.1: react-icons@^4.3.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"