first commit

This commit is contained in:
unknown 2020-08-17 16:57:43 +02:00
commit b33cab060f
19 changed files with 12889 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/aniflix
/compiled

10899
api fetch/css.css Normal file

File diff suppressed because it is too large Load diff

76
api fetch/css1.css Normal file
View file

@ -0,0 +1,76 @@
::-webkit-scrollbar {
display: none
}
* {
scrollbar-width: none;
-ms-overflow-style: none;
}
.bg-tp-dark {
background: rgba(48, 48, 48, 0.6)
}
.bg-black{
background: #222
}
.cover-img {
object-fit: cover;
height: 100%;
width: 100%
}
.contain-img {
object-fit: contain;
width: 100%
}
.banner {
height: 400px;
opacity: 0.85
}
.mt-nc{
margin-top: -150px
}
.view{
position: fixed;
z-index: 9;
height: 100vh;
width: 100%;
top: 0;
left: 0
}
.desc {
max-height: 210px;
overflow: scroll
}
.ovf-y-scroll {
overflow-y: scroll
}
.card-grid {
display: grid;
grid-template-rows: auto 1fr auto;
}
.search {
background: #444 !important;
color: #ADB5BD !important;
border-color: #444;
}
.gallery .card {
cursor: pointer;
transition: transform .2s ease;
}
.gallery .card:hover {
transform: scale(1.05)
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, 540px);
grid-auto-rows: 280px;
justify-content: center;
grid-gap: 2rem;
padding: 2rem;
}

280
api fetch/css2.css Normal file
View file

@ -0,0 +1,280 @@
:root {
font-size: 15px;
font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--nav-size: 5rem;
--accent-color: #e5204c;
--volume-level: 100%;
--progress: 0%;
--buffer: 0%;
color: #b6b6b6;
}
::-webkit-scrollbar {
display: none
}
html,
body {
margin: 0;
background: #222;
}
* {
box-sizing: border-box;
}
.cont {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: row
}
/*NAVBAR*/
nav {
min-width: 5rem;
height: 100vh;
z-index: 100;
}
nav:hover ul {
max-width: 13.4rem;
transition: max-width .2s ease
}
nav ul {
overflow-x: hidden;
max-width: var(--nav-size);
background: #111;
position: absolute;
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
transition: max-width .2s ease;
}
nav li {
width: 100%;
}
nav li:last-child {
margin-top: auto;
}
nav a {
display: flex;
align-items: center;
height: 5rem;
text-decoration: none;
filter: grayscale(100%) opacity(0.7);
color: #b6b6b6;
}
nav a:hover i {
filter: drop-shadow(0 0 4px #888);
}
nav a:hover {
filter: grayscale(0%) opacity(1);
color: #ececec;
}
nav span {
margin: 0 3rem 0 1rem;
font-weight: 600
}
.nav i {
font-size: 2rem !important;
min-width: 2rem;
margin: 0 1.5rem;
}
nav img {
width: 3rem;
margin: 0 1rem;
}
/*PLAYER*/
#player:target {
bottom: 0;
right: 0;
position: relative;
align-self: center;
width: 100%;
height: 100%;
transition: none !important;
display: flex
}
#player {
position: absolute;
display: flex;
bottom: 2rem;
right: 2rem;
width: 25%;
transition: width .2s ease;
z-index: 10
}
#player video {
width: 100%;
}
#player:target a {
display: none;
}
#player a {
position: absolute;
height: 100%;
width: 100%;
z-index: 10
}
#player:target .controls {
display: flex;
font-family: Roboto, monospace
}
#player .controls {
display: none
}
.controls {
position: absolute;
align-self: flex-end;
align-items: flex-end;
user-select: none;
min-width: 100%;
background: linear-gradient(to top, rgba(0, 0, 0, .8), rgba(0, 0, 0, .4) 25%, rgba(0, 0, 0, .2) 50%, rgba(0, 0, 0, .1) 75%, transparent);
opacity: 1;
transition: .5s opacity ease
}
.controls > *:hover {
filter: drop-shadow(0 0 8px #000);
}
.controls span {
font-size: 1.7rem !important;
color: #ececec;
padding: 1rem;
transition: all .2s ease;
cursor: pointer
}
.controls .ts {
font-size: 1.3rem !important;
white-space: nowrap;
align-self: center;
cursor: default;
padding: 0 1rem;
font-weight: 600
}
#prog {
width: 100%;
align-self: center;
padding: 1rem 0;
}
.volume {
display: flex;
width: auto
}
.volume:hover > input[type=range] {
width: 5vw;
display: inline-block;
transition: all .1s ease;
margin-right: 1rem
}
.volume > input[type=range] {
width: 0;
transition: all .1s ease;
margin-right: 0
}
.controls input[type=range] {
-webkit-appearance: none;
background: transparent;
margin: 0
}
.controls input[type=range]:focus {
outline: none;
}
.controls input#vol[type=range]::-webkit-slider-runnable-track {
width: 50%;
height: 3px;
cursor: pointer;
background: linear-gradient(90deg, #e5204c var(--volume-level), rgba(255, 255, 255, .2) var(--volume-level))
}
.controls input#prog[type=range]::-webkit-slider-runnable-track {
width: 50%;
height: 3px;
cursor: pointer;
background: linear-gradient(90deg, #e5204c var(--progress), rgba(255, 255, 255, .4) var(--progress), rgba(255, 255, 255, .4) var(--buffer), rgba(255, 255, 255, .2) var(--buffer))
}
.controls input[type=range]:hover::-webkit-slider-thumb {
height: 12px;
width: 12px;
margin-top: -4px
}
.controls input[type=range]::-webkit-slider-thumb {
height: 0px;
width: 0px;
border-radius: 50%;
background: var(--accent-color);
cursor: pointer;
-webkit-appearance: none;
margin-top: 1px;
transition: all .1s ease
}
/*BROWSE*/
/*CONTAINER*/
#browse {
width: 100%;
height: 100vh;
flex-direction: column;
position: relative;
overflow-y: scroll
}
#browse:target {
display: flex
}
/*PREVIEW*/
/*DETAILS*/
/*SEARCH*/
/*SECTIONS*/
section {
display: none
}

54
api fetch/id.html Normal file
View file

@ -0,0 +1,54 @@
<script>
var query = `
query ($id: Int) { # Define which variables will be used in the query (id)
Media (id: $id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query)
id
title {
romaji
english
native
}
}
}
`;
// Define our query variables and values that will be used in the query request
var variables = {
id: 16498
};
// Define the config we'll need for our Api request
var url = 'https://graphql.anilist.co',
options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query: query,
variables: variables
})
};
// Make the HTTP Api request
fetch(url, options).then(handleResponse)
.then(handleData)
.catch(handleError);
function handleResponse(response) {
return response.json().then(function(json) {
return response.ok ? json : Promise.reject(json);
});
}
function handleData(data) {
console.log(data);
}
function handleError(error) {
alert('Error, check console');
console.error(error);
}
</script>

85
api fetch/index.html Normal file
View file

@ -0,0 +1,85 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<meta name="generator" content="">
<title>API</title>
<link href="css.css" rel="stylesheet">
<link href="css1.css" rel="stylesheet">
<script src='js.js' defer></script>
</head>
<body>
<div class="view bg-dark ovf-y-scroll" hidden>
<div class="banner">
<img class="cover-img">
<button type="button" class="close" onclick="hideAnime()">
<span>&times;</span>
</button>
</div>
<div class="container">
<div class="row">
<div class="col-md-3 mt-nc pb-4">
<img class="contain-img rounded-lg">
</div>
<div class="col-9 py-4">
<h4 class="title">
</h4>
<p class="text-muted desc">
</p>
</div>
</div>
</div>
<div class="bg-black">
<div class="container">
<div class="row py-4">
<div class="col-md-3">
<div class="card">
<div class="card-body text-muted details">
</div>
</div>
</div>
<div class="col-9">
<table class="table table-borderless table-hover rounded-lg overflow-hidden">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Size</th>
<th scope="col">Seed</th>
<th scope="col">Leech</th>
<th scope="col">Downloads</th>
<th scope="col">Play</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="text-center pt-5">
<div class="container">
<form class="input-group mb-3" action="javascript:search()">
<div class="input-group-prepend">
<button class="btn btn-secondary" type="submit">O</button>
</div>
<input type="text" class="form-control search" placeholder="Search" id="search">
</form>
</div>
</div>
<div class="container-fluid h-100">
<div class="gallery">
</div>
</div>
</body>
</html>

155
api fetch/index1.html Normal file
View file

@ -0,0 +1,155 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Titlename</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="css.css">
<link rel="stylesheet" href="css1.css">
<link rel="stylesheet" href="css2.css">
<script src='js.js' defer></script>
<script src="webtorrent.min.js"></script>
<script src="playerHandler.js"></script>
<script src="torrentHandler.js"></script>
</head>
<body>
<div class="cont">
<nav id="nav">
<ul class="nav">
<li>
<a>
<img src="logofinal%20small.png">
<span>Titlename</span>
</a>
</li>
<li>
<a href="#player">
<i class="material-icons">play_arrow</i>
<span>Play</span>
</a>
</li>
<li>
<a href="#releases">
<i class="material-icons">schedule</i>
<span>Releases</span>
</a>
</li>
<li>
<a href="#browse">
<i class="material-icons">list</i>
<span>Browse</span>
</a>
</li>
<li>
<a href="#settings">
<i class="material-icons">settings</i>
<span>Settings</span>
</a>
</li>
</ul>
</nav>
<section id="player">
<a href="#player"></a>
<video id="video" src="hs.mkv" type="video/mp4">
</video>
<div class="controls">
<span class="material-icons ctrl" title="Play/Pause [Space]" id="bpp">
play_arrow
</span>
<span class="material-icons ctrl" title="Next [N]" id="bnext">
skip_next
</span>
<div class="volume">
<span class="material-icons ctrl" title="Mute [M]" id="bmute">
volume_up
</span>
<input type="range" value="100" id="vol">
</div>
<span class="ts" id="elapsed">--:--</span>
<input type="range" min="0" max="1000" value="0" id="prog">
<span class="ts" id="remaining">--:--</span>
<span class="material-icons ctrl" title="Popout Window [P]" id="bpip">
picture_in_picture
</span>
<span class="material-icons ctrl" title="Theatre Mode [T]" id="btheatre">
crop_16_9
</span>
<span class="material-icons ctrl" title="Fullscreen [F]" id="bfull">
fullscreen
</span>
</div>
</section>
<section id="browse">
<div class="view bg-dark ovf-y-scroll" hidden>
<div class="banner">
<img class="cover-img">
<button type="button" class="close" onclick="hideAnime()">
<span>&times;</span>
</button>
</div>
<div class="container">
<div class="row">
<div class="col-md-3 mt-nc pb-4">
<img class="contain-img rounded-lg">
</div>
<div class="col-9 py-4">
<h4 class="title">
</h4>
<p class="text-muted desc">
</p>
</div>
</div>
</div>
<div class="bg-black">
<div class="container">
<div class="row py-4">
<div class="col-md-3">
<div class="card">
<div class="card-body text-muted details">
</div>
</div>
</div>
<div class="col-9">
<table class="table table-borderless table-hover rounded-lg overflow-hidden">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Size</th>
<th scope="col">Seed</th>
<th scope="col">Leech</th>
<th scope="col">Downloads</th>
<th scope="col">Play</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="text-center pt-5">
<div class="container">
<form class="input-group mb-3" action="javascript:search()">
<div class="input-group-prepend">
<button class="btn btn-secondary" type="submit">O</button>
</div>
<input type="text" class="form-control search" placeholder="Search" id="search">
</form>
</div>
</div>
<div class="container-fluid h-100">
<div class="gallery">
</div>
</div>
</section>
</div>
</body>
</html>

230
api fetch/js.js Normal file
View file

@ -0,0 +1,230 @@
var query,
variables = {
type: "ANIME",
page: 1,
perPage: 50
},
request;
var url = 'https://graphql.anilist.co',
options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
query: query,
variables: variables
})
};
function search() {
let search = document.querySelector("#search").value
if (search == "") {
alRequest()
} else {
alRequest(search)
}
}
function alRequest(a) {
if (a == undefined) {
variables.sort = "TRENDING_DESC"
delete variables.search
query = `
query ($page: Int, $perPage: Int, $sort: [MediaSort], $type: MediaType) {
Page (page: $page, perPage: $perPage) {
pageInfo {
total
currentPage
lastPage
hasNextPage
perPage
}
media(type: $type, sort: $sort) {
id
title {
romaji
english
native
}
description(
asHtml: true
)
season
seasonYear
format
status
episodes
duration
averageScore
genres
coverImage {
large
}
bannerImage
}
}
}
`
} else {
variables.search = a
delete variables.sort
query = `
query ($page: Int, $perPage: Int, $search: String, $type: MediaType) {
Page (page: $page, perPage: $perPage) {
pageInfo {
total
currentPage
lastPage
hasNextPage
perPage
}
media (type: $type, search: $search) {
id
title {
romaji
english
native
}
description(
asHtml: true
)
season
seasonYear
format
status
episodes
duration
averageScore
genres
coverImage {
large
}
bannerImage
}
}
}
`
}
options.body = JSON.stringify({
query: query,
variables: variables
})
fetch(url, options).then((handleResponse))
.then(handleData)
.catch((error) => console.error(error));
function handleResponse(response) {
return response.json().then(function (json) {
return response.ok ? json : Promise.reject(json);
});
}
}
function handleData(data) {
request = data
console.log(request);
let frag = document.createDocumentFragment(),
hasBegun = true
try {
data.data.Page.media.forEach((media, index) => {
let template = document.createElement("div")
template.classList.add("card", "bg-dark")
template.innerHTML = `
<div class="row no-gutters h-100">
<div class="col-4 h-100">
<img src="${media.coverImage.large}" class="cover-img">
<div class="card-img-overlay d-flex align-content-end flex-wrap p-0">
<div class="bg-tp-dark d-flex flex-grow-1 px-3 py-2">
${!!media.title.english ? media.title.english : media.title.romaji}
</div>
</div>
</div>
<div class="col-8 h-100 card-grid">
<div class="card-header px-3 pb-1">
<h5 class="m-0 text-capitalize">${(!!media.season ? media.season.toLowerCase()+" ": "") + (!!media.seasonYear ? media.seasonYear : "")}</h5>
<p class="card-text text-muted mb-0 text-capitalize"><small>${((!!media.format ? (media.format == "TV" ? media.format + " Show" : media.format) + " • " : "")+(!!media.episodes ? media.episodes + " Episodes • " : (!!media.duration ? media.duration + " Minutes • " : "" ))+(!!media.status ? media.status.toLowerCase() : ""))}</small></p>
</div>
<div class="card-body ovf-y-scroll px-3 py-2">
<p class="card-text mb-0">${media.description}</p>
</div>
<div class="card-footer px-3 py-2">
${media.genres.map(key => (`<span class="badge badge-pill badge-primary">${key}</span> `)).join('')}
</div>
</div>
</div>
`
template.onclick = function () {
viewAnime(index)
}
frag.appendChild(template)
})
} catch (e) {
console.error(e)
}
if (hasBegun) {
document.querySelector('.gallery').textContent = '';
hasBegun = false;
}
document.querySelector('.gallery').appendChild(frag)
}
function hideAnime(){
document.querySelector(".view").setAttribute("hidden", "")
}
function viewAnime(a) {
let media = request.data.Page.media[a]
let details =["title.english","title.romaji","status","season","seasonYear","episodes","duration","format","averageScore"]
document.querySelector(".view").removeAttribute("hidden")
document.querySelector(".view .banner img").src = media.bannerImage
document.querySelector(".view .contain-img").src = media.coverImage.large
document.querySelector(".view .contain-img").src = media.coverImage.large
document.querySelector(".view .title").textContent = !!media.title.english ? media.title.english : media.title.romaji
document.querySelector(".view .desc").innerHTML = !!media.description ? media.description : ""
tsearch(a,1)
}
const DOMPARSER = new DOMParser().parseFromString.bind(new DOMParser())
function tsearch(a, b) {
let name = request.data.Page.media[a].title.romaji
if (b < 10) {
b = "0" + b
}
let url = new URL("https://nyaa.si/?page=rss&c=1_2&f=2&s=seeders&o=desc&q=" + name + " \" " + b + " \"")
let frag = document.createDocumentFragment(),
hasBegun = true
fetch(url).then((res) => {
res.text().then((xmlTxt) => {
try {
let doc = DOMPARSER(xmlTxt, "text/xml")
doc.querySelectorAll('item').forEach((item, index) => {
let i = item.querySelector.bind(item),
template = document.createElement("tr")
template.innerHTML = `
<th scope="row">${(index+1)}</th>
<td>${i("title").textContent}</td>
<td>${i("size").textContent}</td>
<td>${i("seeders").textContent}</td>
<td>${i("leechers").textContent}</td>
<td>${i("downloads").textContent}</td>
<td onclick="console.log('${i('link').textContent}')">Play</td>
`
frag.appendChild(template)
})
} catch (e) {
console.error(e)
}
if (hasBegun) {
document.querySelector('tbody').textContent = '';
hasBegun = false;
}
document.querySelector('tbody').appendChild(frag)
})
}).catch(() => console.error('Error in fetching the RSS feed'))
}
alRequest()

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

297
api fetch/playerHandler.js Normal file
View file

@ -0,0 +1,297 @@
let controls,
video,
player,
volume;
//create event listeners after page load
document.onreadystatechange = function () {
if (document.readyState == "interactive") {
controls = document.getElementsByClassName('ctrl');
video = document.querySelector('#video');
player = document.querySelector('#player');
volume = document.querySelector('#vol');
progress = document.querySelector('#prog');
volume.addEventListener("input", function () {
updatevolume(null)
});
progress.addEventListener("input", setprogress);
video.addEventListener("playing", playcheck);
video.addEventListener("canplay", updateDisplay);
video.addEventListener("loadedmetadata", setduration);
video.addEventListener("click", bpp)
immerse();
for (let i = 0; i < controls.length; i++) {
controls[i].addEventListener("click", function () {
let func = this.id;
window[func]()
})
}
}
}
//immerse timeout
let immersetime;
function immerse() {
document.onmousemove = resetTimer;
document.onkeypress = resetTimer;
function immerseplayer() {
document.querySelector('#player').classList.add('immersed')
}
function resetTimer() {
clearTimeout(immersetime);
document.querySelector('#player').classList.remove('immersed')
immersetime = setTimeout(immerseplayer, 3000)
}
}
//set duration
let duration;
function setduration() {
duration = video.duration;
}
//progress
function setprogress() {
video.currentTime = progress.value / 1000 * duration;
updateDisplay();
}
function updateDisplay() {
let progresspercent = (video.currentTime / duration * 100).toFixed(4),
bufferpercent = (video.buffered.end(0) / duration * 100).toFixed(4),
remaining = duration - video.currentTime
document.documentElement.style.setProperty("--progress", progresspercent + "%");
document.documentElement.style.setProperty("--buffer", bufferpercent + "%");
document.querySelector("#elapsed").innerHTML = toTS(video.currentTime);
document.querySelector("#remaining").innerHTML = toTS(remaining);
progress.value = Math.floor(progresspercent * 10)
}
function toTS(sec) {
var hours = Math.floor(sec / 3600);
var minutes = Math.floor((sec - (hours * 3600)) / 60);
var seconds = Math.floor(sec - (hours * 3600) - (minutes * 60));
if (minutes < 10) {
minutes = "0" + minutes;
}
if (seconds < 10) {
seconds = "0" + seconds;
}
if (hours > 0) {
return hours + ':' + minutes + ':' + seconds;
} else {
return minutes + ':' + seconds;
}
}
let islooped;
function playcheck() {
if (!islooped && !video.paused) {
islooped = true;
updateDisplay();
setTimeout(function () {
islooped = false;
playcheck();
}, 50)
}
}
//play/pause button
function bpp() {
let btnpp = document.querySelector('#bpp')
if (video.paused) {
btnpp.innerHTML = "pause";
video.play();
} else {
btnpp.innerHTML = "play_arrow";
video.pause();
}
}
function bnext() {
console.log("todo")
// TODO: get magnet link of next current next episode [using search]
}
//volume shit
let oldlevel;
function bmute() {
if (video.volume == 0) {
updatevolume(oldlevel)
} else {
oldlevel = video.volume * 100
updatevolume(0)
}
}
function updatevolume(a) {
let btnm = document.querySelector("#bmute"),
level;
if (a == null) {
level = volume.value;
} else {
level = a;
volume.value = a;
}
document.documentElement.style.setProperty("--volume-level", level + "%");
btnm.innerHTML = (level == 0) ? "volume_off" : "volume_up";
video.volume = level / 100
}
//PiP
function bpip() {
if (!document.pictureInPictureElement) {
video.requestPictureInPicture();
} else {
if (document.pictureInPictureElement) {
document.exitPictureInPicture();
}
}
}
//theathe mode
function btheatre() {
let nav = document.querySelector('#nav');
nav.classList.toggle('theatre');
}
//fullscreen
function bfull() {
let btnfull = document.querySelector('#bfull')
if (!document.fullscreenElement) {
player.requestFullscreen();
btnfull.innerHTML = "fullscreen_exit"
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
btnfull.innerHTML = "fullscreen"
}
}
}
function seek(a) {
video.currentTime += a;
}
//bar related shit
function getPositionP(e) {
let element = e.target.getBoundingClientRect();
let x = (e.offsetX / element.width).toFixed(5);
return {
x
};
}
function printPosition(e) {
let positionP = getPositionP(e);
e.onmousemove = function () {
document.querySelector('#vol').innerHTML = positionP.x
}
}
//keybinds
document.onkeydown = function (a) {
switch (a.key) {
case " ":
bpp();
break;
case "n":
bnext();
break;
case "m":
bmute();
break;
case "p":
bpip();
break;
case "t":
btheatre();
break;
case "f":
bfull();
break;
case "s":
seek(89);
break;
case "ArrowLeft":
seek(-2);
break;
case "ArrowRight":
seek(2);
}
}
//media session
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: 'Never Gonna Give You Up',
artist: 'Rick Astley',
album: 'Whenever You Need Somebody',
artwork: [
{
src: 'https://dummyimage.com/96x96',
sizes: '96x96',
type: 'image/png'
},
{
src: 'https://dummyimage.com/128x128',
sizes: '128x128',
type: 'image/png'
},
{
src: 'https://dummyimage.com/192x192',
sizes: '192x192',
type: 'image/png'
},
{
src: 'https://dummyimage.com/256x256',
sizes: '256x256',
type: 'image/png'
},
{
src: 'https://dummyimage.com/384x384',
sizes: '384x384',
type: 'image/png'
},
{
src: 'https://dummyimage.com/512x512',
sizes: '512x512',
type: 'image/png'
},
]
});
navigator.mediaSession.setActionHandler('play', function () {
bpp();
});
navigator.mediaSession.setActionHandler('pause', function () {
bpp();
});
navigator.mediaSession.setActionHandler('seekbackward', function () {
seek(-2);
});
navigator.mediaSession.setActionHandler('seekforward', function () {
seek(2);
});
navigator.mediaSession.setActionHandler('nexttrack', function () {
bnext();
});
}

77
api fetch/search.html Normal file
View file

@ -0,0 +1,77 @@
<script>
var query = `
query ($page: Int, $perPage: Int, $search: String, $type: MediaType) {
Page (page: $page, perPage: $perPage) {
pageInfo {
total
currentPage
lastPage
hasNextPage
perPage
}
media (type: $type, search: $search) {
id
title {
romaji
english
native
}
description(
asHtml: true
)
season
seasonYear
format
status
episodes
duration
averageScore
genres
coverImage {
large
}
bannerImage
}
}
}
`;
var variables = {
search: "Fate/Zero",
type: "ANIME",
page: 1,
perPage: 50
};
var url = 'https://graphql.anilist.co',
options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query: query,
variables: variables
})
};
fetch(url, options).then(handleResponse)
.then(handleData)
.catch(handleError);
function handleResponse(response) {
return response.json().then(function(json) {
return response.ok ? json : Promise.reject(json);
});
}
function handleData(data) {
console.log(data);
}
function handleError(error) {
console.error(error);
}
</script>

View file

@ -0,0 +1,26 @@
var client = new WebTorrent()
let lastTorrent;
function playTorrent(magnetlink, hasName) {
if (lastTorrent) {
client.remove(magnetlink)
}
lastTorrent = magnetlink
console.log(magnetlink)
client.add(magnetlink, function (torrent) {
console.log('Client is downloading:', torrent.infoHash)
var file = torrent.files.find(function (file) {
return file.name.endsWith('.mp4'||'.mkv')
})
file.renderTo('video', {
autoplay: true,
controls: false
});
})
}
client.on('error', function (err) {
console.error('ERROR: ' + err.message)
})
//'magnet:?xt=urn:btih:W4YM35DEUBTUS6BY2YEBHD5KVWQANCOE&tr=http://nyaa.tracker.wf:7777/announce&tr=udp://tracker.coppersurfer.tk:6969/announce&tr=udp://tracker.internetwarriors.net:1337/announce&tr=udp://tracker.leechersparadise.org:6969/announce&tr=udp://tracker.opentrackr.org:1337/announce&tr=udp://open.stealth.si:80/announce&tr=udp://p4p.arenabg.com:1337/announce&tr=udp://mgtracker.org:6969/announce&tr=udp://tracker.tiny-vps.com:6969/announce&tr=udp://peerfect.org:6969/announce&tr=http://share.camoe.cn:8080/announce&tr=http://t.nyaatracker.com:80/announce&tr=https://open.kickasstracker.com:443/announce&ix=0'
//'magnet:?xt=urn:btih:OWRWEI2ABTSOUYBG6MMBRPC3LT22HMGV&tr=http://nyaa.tracker.wf:7777/announce&tr=udp://tracker.coppersurfer.tk:6969/announce&tr=udp://tracker.internetwarriors.net:1337/announce&tr=udp://tracker.leechersparadise.org:6969/announce&tr=udp://tracker.opentrackr.org:1337/announce&tr=udp://open.stealth.si:80/announce&tr=udp://p4p.arenabg.com:1337/announce&tr=udp://mgtracker.org:6969/announce&tr=udp://tracker.tiny-vps.com:6969/announce&tr=udp://peerfect.org:6969/announce&tr=http://share.camoe.cn:8080/announce&tr=http://t.nyaatracker.com:80/announce&tr=https://open.kickasstracker.com:443/announce'

20
api fetch/trending.html Normal file
View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>API request</title>
<script src='js.js' defer></script>
<link rel='stylesheet' href='css.css'>
</head>
<body>
<h1>Anime</h1>
<input type="text" placeholder="Name" id="searchName"><button onclick="search()">Search</button>
<div id="results">
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

6
api fetch/webtorrent.min.js vendored Normal file

File diff suppressed because one or more lines are too long

221
test/index - Copy.html Normal file
View file

@ -0,0 +1,221 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>browser server test</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webtorrent/0.102.4/webtorrent.min.js"></script>
<script src="range-parser.js"></script>
<script>
const client = new WebTorrent()
const announceList = [
['udp://tracker.openbittorrent.com:80'],
['udp://tracker.internetwarriors.net:1337'],
['udp://tracker.leechers-paradise.org:6969'],
['udp://tracker.coppersurfer.tk:6969'],
['udp://exodus.desync.com:6969'],
['wss://tracker.webtorrent.io'],
['wss://tracker.btorrent.xyz'],
['wss://tracker.openwebtorrent.com'],
['wss://tracker.fastcast.nz']
]
WEBTORRENT_ANNOUNCE = announceList
.map(function (arr) {
return arr[0]
})
.filter(function (url) {
return url.indexOf('wss://') === 0 || url.indexOf('ws://') === 0
})
const sintel =
'magnet:?xt=urn:btih:da225bd2c3ce14a4cf41fd0a32e0c9811b186812&dn=%5BHorribleSubs%5D%20Sword%20Art%20Online%20-%20Alicization%20-%20War%20of%20Underworld%20-%2018%20%5B1080p%5D.mkv&tr=http%3A%2F%2Fnyaa.tracker.wf%3A7777%2Fannounce&tr=udp%3A%2F%2Fopen.stealth.si%3A80%2Fannounce&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337%2Fannounce&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969%2Fannounce&tr=udp%3A%2F%2Fexodus.desync.com%3A6969%2Fannounce'
// const sintel =
// 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F'
client.add(sintel, async function (torrent) {
const server = await torrent.createServer()
await server.listen()
const a = document.createElement('a')
a.href = a.innerText = `./webtorrent/${torrent.infoHash}/`
a.innerText += ' (opens in a new tab - since you need to have this page open for webtorrent to work)'
a.target = '_blank'
document.body.appendChild(a)
const video = document.createElement('video')
video.controls = true
video.src = `./webtorrent/${torrent.infoHash}/${torrent.files[0].path}`
document.body.appendChild(video)
})
// Wish there where a easier way to get some of webtorrent's classes so i can patch stuff
// const WebTorrent = require('webtorrent')
// const { Torrent } = WebTorrent
const dummyTorrent = client.add('06d67cc41f44fd57241551b6d95c2d1de38121ae')
const torrentPrototype = Object.getPrototypeOf(dummyTorrent)
client.remove('06d67cc41f44fd57241551b6d95c2d1de38121ae')
function getPageHTML(title, pageHtml) {
return "<!DOCTYPE html><html><head><meta><title>" + title + "</title></head><body>" + pageHtml + "<body></html>";
}
// From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function encodeRFC5987(str) {
return encodeURIComponent(str)
// Note that although RFC3986 reserves "!", RFC5987 does not,
// so we do not need to escape it
.replace(/['()]/g, escape) // i.e., %27 %28 %29
.replace(/\*/g, '%2A')
// The following are not required for percent-encoding per RFC5987,
// so we can allow for a little better readability over the wire: |`^
.replace(/%(?:7C|60|5E)/g, unescape)
}
torrentPrototype.createServer = function (requestListener) {
if (this.destroyed) throw new Error('torrent is destroyed')
let registration = null
const torrent = this
function serveIndexPage() {
const listHtml = torrent.files.map((file, i) =>
`<li><a x_download="${file.name}" href="${registration.scope}webtorrent/${torrent.infoHash}/${file.path}">${file.path}</a> (${file.length} bytes)</li>`
).join('<br>')
const body = getPageHTML(
`${torrent.name} - WebTorrent`,
`<h1>${torrent.name}</h1><ol>${listHtml}</ol>`
)
return {
status: 200,
headers: {
'Content-Type': 'text/html'
},
body,
}
}
function serve404Page() {
return {
status: 404,
headers: {
'Content-Type': 'text/html'
},
body: getPageHTML('404 - Not Found', '<h1>404 - Not Found</h1>')
}
}
function serveFile(file, req) {
const res = {
status: 200,
headers: {
'Content-Type': 'video/mp4',
// Support range-requests
'Accept-Ranges': 'bytes',
// Set name of file (for "Save Page As..." dialog)
'Content-Disposition': `inline; filename*=UTF-8''${encodeRFC5987(file.name)}`
}
}
// `rangeParser` returns an array of ranges, or an error code (number) if
// there was an error parsing the range.
let range = rangeParser(file.length, new Headers(req.headers).get('range') || '')
if (Array.isArray(range)) {
res.status = 206 // indicates that range-request was understood
// no support for multi-range request, just use the first range
range = range[0]
res.headers['Content-Range'] = `bytes ${range.start}-${range.end}/${file.length}`
res.headers['Content-Length'] = `${range.end - range.start + 1}`
} else {
range = null
res.headers['Content-Length'] = file.length
}
if (req.method === 'HEAD') res.body = ''
else res.stream = file.createReadStream(range)
return res
}
navigator.serviceWorker.addEventListener('message', evt => {
const root = new URL(registration.scope).pathname
const url = new URL(evt.data.url)
const pathname = url.pathname.split(`webtorrent/${torrent.infoHash}/`)[1]
console.log(pathname)
const respond = msg => evt.ports[0].postMessage(msg)
if (pathname === '') {
return respond(serveIndexPage())
}
const file = torrent.files[0]
const res = serveFile(file, evt.data)
if (res.stream) {
const stream = res.stream
delete res.stream
stream.once('end', () => {
respond(null) // Signal end event
evt.ports[0].onmessage = null
})
evt.ports[0].onmessage = evt => {
const chunk = stream.read()
if (chunk === null) {
stream.once('readable', () => {
const chunk = stream.read()
respond(new Uint8Array(chunk))
})
} else {
respond(new Uint8Array(chunk))
}
}
}
respond(res)
})
const res = {
listen(port) {
const scope = `./`
res.scope = scope
return navigator.serviceWorker.getRegistration(scope).then(swReg => {
return swReg || navigator.serviceWorker.register('sw.js', {
scope
})
}).then(swReg => {
registration = swReg
res.scope = registration.scope
res.registration = registration
let swRegTmp = swReg.installing || swReg.waiting
if (swReg.active)
return
return new Promise(rs => {
swRegTmp.onstatechange = () => {
if (swRegTmp.state === 'activated') rs()
}
})
})
},
close() {
registration && registration.unregister()
}
}
return res
}
</script>
</body>
</html>

186
test/index.html Normal file
View file

@ -0,0 +1,186 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>browser server test</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webtorrent/0.102.4/webtorrent.min.js"></script>
<script src="range-parser.js"></script>
<script>
const client = new WebTorrent()
const sintel = 'magnet:?xt=urn:btih:08ada5a7a6183aae1e09d831df6748d566095a10&dn=Sintel&tr=udp%3A%2F%2Fexplodie.org%3A6969&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.empire-js.us%3A1337&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.opentrackr.org%3A1337&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com&ws=https%3A%2F%2Fwebtorrent.io%2Ftorrents%2F'
client.add(sintel, async function(torrent) {
const server = await torrent.createServer()
await server.listen()
const a = document.createElement('a')
a.href = a.innerText = `./webtorrent/${torrent.infoHash}/`
a.innerText += ' (opens in a new tab - since you need to have this page open for webtorrent to work)'
a.target = '_blank'
document.body.appendChild(a)
const video = document.createElement('video')
video.controls = true
video.src = `./webtorrent/${torrent.infoHash}/${torrent.files[5].path}`
document.body.appendChild(video)
})
// Wish there where a easier way to get some of webtorrent's classes so i can patch stuff
// const WebTorrent = require('webtorrent')
// const { Torrent } = WebTorrent
const dummyTorrent = client.add('06d67cc41f44fd57241551b6d95c2d1de38121ae')
const torrentPrototype = Object.getPrototypeOf(dummyTorrent)
client.remove('06d67cc41f44fd57241551b6d95c2d1de38121ae')
function getPageHTML (title, pageHtml) {
return "<!DOCTYPE html><html><head><meta><title>" + title + "</title></head><body>" + pageHtml + "<body></html>";
}
// From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function encodeRFC5987 (str) {
return encodeURIComponent(str)
// Note that although RFC3986 reserves "!", RFC5987 does not,
// so we do not need to escape it
.replace(/['()]/g, escape) // i.e., %27 %28 %29
.replace(/\*/g, '%2A')
// The following are not required for percent-encoding per RFC5987,
// so we can allow for a little better readability over the wire: |`^
.replace(/%(?:7C|60|5E)/g, unescape)
}
torrentPrototype.createServer = function(requestListener) {
if (this.destroyed) throw new Error('torrent is destroyed')
let registration = null
const torrent = this
function serveIndexPage () {
const listHtml = torrent.files.map((file, i) => `<li><a x_download="${file.name}" href="${registration.scope}webtorrent/${torrent.infoHash}/${file.path}">${file.path}</a> (${file.length} bytes)</li>`).join('<br>')
const body = getPageHTML(
`${torrent.name} - WebTorrent`,
`<h1>${torrent.name}</h1><ol>${listHtml}</ol>`
)
return {
status: 200,
headers: {'Content-Type': 'text/html'},
body,
}
}
function serve404Page () {
return {
status: 404,
headers: {'Content-Type': 'text/html'},
body: getPageHTML('404 - Not Found', '<h1>404 - Not Found</h1>')
}
}
function serveFile (file, req) {
const res = {
status: 200,
headers: {
'Content-Type': file._getMimeType(),
// Support range-requests
'Accept-Ranges': 'bytes',
// Set name of file (for "Save Page As..." dialog)
'Content-Disposition': `inline; filename*=UTF-8''${encodeRFC5987(file.name)}`
}
}
// `rangeParser` returns an array of ranges, or an error code (number) if
// there was an error parsing the range.
let range = rangeParser(file.length, new Headers(req.headers).get('range') || '')
if (Array.isArray(range)) {
res.status = 206 // indicates that range-request was understood
// no support for multi-range request, just use the first range
range = range[0]
res.headers['Content-Range'] = `bytes ${range.start}-${range.end}/${file.length}`
res.headers['Content-Length'] = `${range.end - range.start + 1}`
} else {
range = null
res.headers['Content-Length'] = file.length
}
if (req.method === 'HEAD') res.body = ''
else res.stream = file.createReadStream(range)
return res
}
navigator.serviceWorker.addEventListener('message', evt => {
const root = new URL(registration.scope).pathname
const url = new URL(evt.data.url)
const pathname = url.pathname.split(`webtorrent/${torrent.infoHash}/`)[1]
const respond = msg => evt.ports[0].postMessage(msg)
if (pathname === '') {
return respond(serveIndexPage())
}
const file = torrent.files.find(f => f.path === pathname)
const res = serveFile(file, evt.data)
if (res.stream) {
const stream = res.stream
delete res.stream
stream.once('end', () => {
respond(null) // Signal end event
evt.ports[0].onmessage = null
})
evt.ports[0].onmessage = evt => {
const chunk = stream.read()
if (chunk === null) {
stream.once('readable', () => {
const chunk = stream.read()
respond(new Uint8Array(chunk))
})
} else {
respond(new Uint8Array(chunk))
}
}
}
respond(res)
})
const res = {
listen(port) {
const scope = `./`
res.scope = scope
return navigator.serviceWorker.getRegistration(scope).then(swReg => {
return swReg || navigator.serviceWorker.register('sw.js', { scope })
}).then(swReg => {
registration = swReg
res.scope = registration.scope
res.registration = registration
let swRegTmp = swReg.installing || swReg.waiting
if (swReg.active)
return
return new Promise(rs => {
swRegTmp.onstatechange = () => {
if (swRegTmp.state === 'activated') rs()
}
})
})
},
close() {
registration && registration.unregister()
}
}
return res
}
</script>
</body>
</html>

162
test/range-parser.js Normal file
View file

@ -0,0 +1,162 @@
/*!
* range-parser
* Copyright(c) 2012-2014 TJ Holowaychuk
* Copyright(c) 2015-2016 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module exports.
* @public
*/
// module.exports = rangeParser
/**
* Parse "Range" header `str` relative to the given file `size`.
*
* @param {Number} size
* @param {String} str
* @param {Object} [options]
* @return {Array}
* @public
*/
function rangeParser (size, str, options) {
if (typeof str !== 'string') {
throw new TypeError('argument str must be a string')
}
var index = str.indexOf('=')
if (index === -1) {
return -2
}
// split the range string
var arr = str.slice(index + 1).split(',')
var ranges = []
// add ranges type
ranges.type = str.slice(0, index)
// parse all ranges
for (var i = 0; i < arr.length; i++) {
var range = arr[i].split('-')
var start = parseInt(range[0], 10)
var end = parseInt(range[1], 10)
// -nnn
if (isNaN(start)) {
start = size - end
end = size - 1
// nnn-
} else if (isNaN(end)) {
end = size - 1
}
// limit last-byte-pos to current length
if (end > size - 1) {
end = size - 1
}
// invalid or unsatisifiable
if (isNaN(start) || isNaN(end) || start > end || start < 0) {
continue
}
// add range
ranges.push({
start: start,
end: end
})
}
if (ranges.length < 1) {
// unsatisifiable
return -1
}
return options && options.combine
? combineRanges(ranges)
: ranges
}
/**
* Combine overlapping & adjacent ranges.
* @private
*/
function combineRanges (ranges) {
var ordered = ranges.map(mapWithIndex).sort(sortByRangeStart)
for (var j = 0, i = 1; i < ordered.length; i++) {
var range = ordered[i]
var current = ordered[j]
if (range.start > current.end + 1) {
// next range
ordered[++j] = range
} else if (range.end > current.end) {
// extend range
current.end = range.end
current.index = Math.min(current.index, range.index)
}
}
// trim ordered array
ordered.length = j + 1
// generate combined range
var combined = ordered.sort(sortByRangeIndex).map(mapWithoutIndex)
// copy ranges type
combined.type = ranges.type
return combined
}
/**
* Map function to add index value to ranges.
* @private
*/
function mapWithIndex (range, index) {
return {
start: range.start,
end: range.end,
index: index
}
}
/**
* Map function to remove index value from ranges.
* @private
*/
function mapWithoutIndex (range) {
return {
start: range.start,
end: range.end
}
}
/**
* Sort function to sort ranges by index.
* @private
*/
function sortByRangeIndex (a, b) {
return a.index - b.index
}
/**
* Sort function to sort ranges by start position.
* @private
*/
function sortByRangeStart (a, b) {
return a.start - b.start
}

60
test/sw.js Normal file
View file

@ -0,0 +1,60 @@
// Activate event
// Be sure to call self.clients.claim()
self.addEventListener('activate', evt => {
// `claim()` sets this worker as the active worker for all clients that
// match the workers scope and triggers an `oncontrollerchange` event for
// the clients.
return self.clients.claim()
})
self.addEventListener('fetch', evt => {
const { request } = evt
const { url, method } = request
const headers = [...request.headers]
if (!url.includes(self.registration.scope + 'webtorrent/')) return null
function getConsumer(clients) {
return new Promise((rs, rj) => {
// Use race condition for whoever controls the response stream
for (const client of clients) {
const mc = new MessageChannel()
mc.port1.onmessage = evt => rs([evt.data, mc])
client.postMessage({
url,
method,
headers,
scope: self.registration.scope
}, [mc.port2])
}
})
}
evt.respondWith(
clients.matchAll({ type: 'window', includeUncontrolled: true })
.then(getConsumer)
.then(([data, consumer]) => {
const readable = 'body' in data ? data.body : new ReadableStream({
pull(controller) {
console.log('requesting data')
return new Promise(rs => {
consumer.port1.onmessage = evt => {
evt.data
? controller.enqueue(evt.data) // evt.data is Uint8Array
: controller.close() // evt.data is null, means the stream ended
rs()
}
consumer.port1.postMessage(true) // send a pull request
})
},
cancel() {
// This event is never executed
console.log('request aborted')
consumer.port1.postMessage(false) // send a cancel request
}
})
return new Response(readable, data)
})
.catch(console.error)
)
})