Initial Commit
This commit is contained in:
commit
94ec89d6c8
79
.gitignore
vendored
Normal file
79
.gitignore
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
324
SongRequestsBuddy.html
Normal file
324
SongRequestsBuddy.html
Normal file
@ -0,0 +1,324 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Song Requests Buddy</title>
|
||||
<script>
|
||||
/* ========================== */
|
||||
/* SetUp your Streamer here */
|
||||
/* ========================== */
|
||||
const twitchName = "murmelmaus_gina";
|
||||
|
||||
// API to StreamerSongList
|
||||
const streamerSongListApi = "https://api.streamersonglist.com/v1/streamers/";
|
||||
|
||||
// Invidious instance to use for youtube videos
|
||||
// Acts as proxy to work locally
|
||||
const invidiousUrl = 'https://inv.nadeko.net';
|
||||
</script>
|
||||
<style>
|
||||
/* ========================== */
|
||||
/* Customize your colors here */
|
||||
/* ========================== */
|
||||
:root {
|
||||
--bg-primary: #1a1a1a;
|
||||
--bg-secondary: #242424;
|
||||
--text-primary: #ebdffa;
|
||||
--accent: #c1aed7;
|
||||
--accent-primary: #51386e;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-primary);
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--text-primary);
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid var(--accent);
|
||||
color: var(--text-primary);
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="max-width: 1024px;margin: 0 auto;padding: 20px;">
|
||||
<h1>Song Requests Buddy</h1>
|
||||
|
||||
<div id="selected-song">
|
||||
<h2>No Song selected</h2>
|
||||
<div style="width: 1024px; height: 576px; background-color: black"></div>
|
||||
</div>
|
||||
|
||||
<h2>From StreamerSonglist Queue</h2>
|
||||
<div id="queue" style="margin-top: 20px"></div>
|
||||
|
||||
<h2 id="songs-title">Songs</h2>
|
||||
<label for="search"></label><input type="text" id="search" style="width: 1024px;align-self: center;height: 30px;"
|
||||
placeholder="Search..." onkeyup="search(this.value)">
|
||||
<div id="songs" style="margin-top: 20px"></div>
|
||||
|
||||
<iframe width="100%" height="auto" allowfullscreen style="max-width: 100%;aspect-ratio: 16 / 9;" id="youtube"
|
||||
hidden></iframe>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const songs = new Map();
|
||||
const attributes = new Map();
|
||||
|
||||
function updateQueue() {
|
||||
const url = streamerSongListApi + twitchName + "/queue";
|
||||
let queueRequest = new XMLHttpRequest();
|
||||
queueRequest.onreadystatechange = function () {
|
||||
if (this.readyState === 4 && this.status === 200) {
|
||||
document.getElementById('queue').innerHTML = '';
|
||||
const data = JSON.parse(this.responseText);
|
||||
data.list.sort(function (a, b) {
|
||||
return a.position - b.position;
|
||||
}).forEach(function (item) {
|
||||
let results = []
|
||||
for (const [name, _] of songs) {
|
||||
if (name.toLowerCase().includes(item.song.title.toLowerCase())) {
|
||||
results.push(songs.values().find(it => it.id === item.song.id));
|
||||
}
|
||||
}
|
||||
if (results.length > 0) {
|
||||
for (const song of results) {
|
||||
if (!(song.artist + " - " + song.title).toLowerCase().includes(item.song.artist.toLowerCase())) {
|
||||
results.remove(songs.values().find(it => it.id === item.song.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (results.length === 0) {
|
||||
results.push(songs.values().find(it => it.id === item.song.id));
|
||||
}
|
||||
songList(results[0], document.getElementById('queue'));
|
||||
})
|
||||
if (data.list.length === 0) {
|
||||
document.getElementById('queue').appendChild(document.createElement('div')).innerText = 'No songs in queue';
|
||||
}
|
||||
}
|
||||
}
|
||||
queueRequest.open("GET", url, true);
|
||||
queueRequest.send();
|
||||
}
|
||||
|
||||
function songList(it, parent) {
|
||||
let song = document.createElement('button');
|
||||
song.id = (it.artist + " - " + it.title).replace(/\s/g, '-').toLowerCase() + '-link';
|
||||
let innerHTML = "<div style='display: inline-flex; align-items: center'><div style='text-align: left'>" + it.artist + " - " + it.title;
|
||||
innerHTML += `<div style='font-size: small; text-align: left'>Last played: ${new Date(it.lastPlayed).toDateString()} | Times played: ${it.timesPlayed}</div></div>`;
|
||||
it.attributeIds.forEach(function (id) {
|
||||
if (attributes.has(id)) {
|
||||
innerHTML += " <div style='background: #ffffff88; margin: 0 10px; border-radius: 5px; padding: 5px; height: 25px'>" + attributes.get(id) + "</div>";
|
||||
}
|
||||
})
|
||||
song.innerHTML = innerHTML;
|
||||
song.style.cursor = 'pointer';
|
||||
song.style.fontSize = "1.2em";
|
||||
song.style.fontWeight = "bold";
|
||||
song.style.textDecoration = "none";
|
||||
song.style.margin = "10px";
|
||||
song.style.height = "69px";
|
||||
song.onclick = function () {
|
||||
showSong(it)
|
||||
};
|
||||
parent.appendChild(song);
|
||||
parent.appendChild(document.createElement('br'));
|
||||
}
|
||||
|
||||
function search(query) {
|
||||
const results = [];
|
||||
for (const [name, song] of songs) {
|
||||
if (query.trim() !== '' && name.toLowerCase().includes(query.toLowerCase())) {
|
||||
results.push(song);
|
||||
}
|
||||
}
|
||||
document.getElementById('songs-title').innerText = 'Songs (' + results.length + '/' + songs.size + ')';
|
||||
const parent = document.getElementById('songs');
|
||||
parent.innerHTML = '';
|
||||
if (results.length === 0) {
|
||||
const noResults = document.createElement('div');
|
||||
noResults.innerText = 'No results';
|
||||
parent.appendChild(noResults);
|
||||
return;
|
||||
}
|
||||
results.forEach(it => {
|
||||
songList(it, document.getElementById('songs'));
|
||||
})
|
||||
}
|
||||
|
||||
function appendVersions(links, song) {
|
||||
let parent = document.getElementById('selected-song');
|
||||
for (let i = 0; i < links.length; i++) {
|
||||
let button = document.createElement("button");
|
||||
button.innerText = "Version " + (i + 1);
|
||||
button.style.fontWeight = "bold";
|
||||
button.style.textDecoration = "none";
|
||||
button.onclick = function () {
|
||||
showSong(song, i)
|
||||
}
|
||||
parent.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
function showSong(song, index = 0) {
|
||||
const url = streamerSongListApi + twitchName + "/songs/" + song.id;
|
||||
let songsRequest = new XMLHttpRequest();
|
||||
songsRequest.onreadystatechange = function () {
|
||||
if (this.readyState === 4 && this.status === 200) {
|
||||
const data = JSON.parse(this.responseText);
|
||||
if (!data.capo || data.capo.length === 0) {
|
||||
showNotInSongList(song.artist + " - " + song.title);
|
||||
} else {
|
||||
if (data.capo.startsWith("https://")) {
|
||||
let links = data.capo.split(" ");
|
||||
showYoutube(song.artist + " - " + song.title, links[index]);
|
||||
appendVersions(links, song);
|
||||
} else {
|
||||
showLocal(song.artist + " - " + song.title, data.capo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
songsRequest.open("GET", url, true);
|
||||
songsRequest.send();
|
||||
}
|
||||
|
||||
function showNotInSongList(name) {
|
||||
let parent = document.getElementById('selected-song');
|
||||
parent.innerHTML = '';
|
||||
let song = document.createElement('div');
|
||||
song.id = name.replace(/\s/g, '-').toLowerCase()
|
||||
let title = document.createElement('h2');
|
||||
title.innerText = name;
|
||||
song.appendChild(title);
|
||||
let video = document.createElement('div');
|
||||
video.style.color = 'white';
|
||||
video.style.backgroundColor = 'black';
|
||||
video.style.textAlign = 'center';
|
||||
video.style.alignContent = 'center';
|
||||
video.style.fontSize = '1.2em';
|
||||
video.style.fontWeight = 'bold';
|
||||
video.style.width = '1024px';
|
||||
video.style.height = '576px';
|
||||
video.innerText = 'There is no Video connected to this Song. Please add it to the "Capo" field.';
|
||||
video.appendChild(document.createElement('br'));
|
||||
let search = document.createElement('a');
|
||||
search.innerText = 'Search for this Song on YouTube in a new Tab';
|
||||
search.target = '_blank';
|
||||
search.href = "https://www.youtube.com/results?search_query=" + encodeURIComponent(name + " karaoke");
|
||||
video.appendChild(search);
|
||||
song.appendChild(video);
|
||||
parent.appendChild(song);
|
||||
}
|
||||
|
||||
function showYoutube(name, link) {
|
||||
let parent = document.getElementById('selected-song');
|
||||
parent.innerHTML = '';
|
||||
let song = document.createElement('div');
|
||||
song.id = name.replace(/\s/g, '-').toLowerCase()
|
||||
let title = document.createElement('h2');
|
||||
title.innerText = name;
|
||||
song.appendChild(title);
|
||||
let iframe = document.getElementById('youtube').cloneNode(false);
|
||||
iframe.hidden = false;
|
||||
iframe.src = invidiousUrl + '/embed/' + link.split("?v=")[1].split("&")[0] + '?iv_load_policy=3&related_videos=false&thin_mode=true&player_style=youtube&t=0';
|
||||
iframe.referrerpolicy = "no-referrer-when-downgrade"
|
||||
iframe.frameborder = "0"
|
||||
song.appendChild(iframe);
|
||||
parent.appendChild(song);
|
||||
}
|
||||
|
||||
function showLocal(name, link) {
|
||||
let parent = document.getElementById('selected-song');
|
||||
parent.innerHTML = '';
|
||||
let song = document.createElement('div');
|
||||
song.id = name.replace(/\s/g, '-').toLowerCase()
|
||||
let title = document.createElement('h2');
|
||||
title.innerText = name;
|
||||
song.appendChild(title);
|
||||
let video = document.createElement('video');
|
||||
video.controls = true;
|
||||
video.width = 1024;
|
||||
video.height = 572;
|
||||
let source = document.createElement('source');
|
||||
source.src = link;
|
||||
video.appendChild(source);
|
||||
song.appendChild(video);
|
||||
parent.appendChild(song);
|
||||
}
|
||||
|
||||
function initSongData(page = 0) {
|
||||
const url = streamerSongListApi + twitchName + "/songs?size=100";
|
||||
let songsRequest = new XMLHttpRequest();
|
||||
songsRequest.onreadystatechange = function () {
|
||||
if (this.readyState === 4 && this.status === 200) {
|
||||
const data = JSON.parse(this.responseText);
|
||||
if (data.total > (page + 1) * 100) {
|
||||
initSongData(page + 1);
|
||||
}
|
||||
data.items.forEach(function (item) {
|
||||
songs.set(item.artist + " - " + item.title, item)
|
||||
})
|
||||
}
|
||||
}
|
||||
songsRequest.open("GET", url + "¤t=" + page, true);
|
||||
songsRequest.send();
|
||||
}
|
||||
|
||||
function initAttributeData() {
|
||||
const url = streamerSongListApi + twitchName;
|
||||
let songsRequest = new XMLHttpRequest();
|
||||
songsRequest.onreadystatechange = function () {
|
||||
if (this.readyState === 4 && this.status === 200) {
|
||||
const data = JSON.parse(this.responseText);
|
||||
data.attributes.forEach(function (item) {
|
||||
if (item.show) {
|
||||
attributes.set(item.id, item.name);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
songsRequest.open("GET", url, true);
|
||||
songsRequest.send();
|
||||
}
|
||||
|
||||
function initData() {
|
||||
initAttributeData();
|
||||
initSongData();
|
||||
|
||||
document.getElementById('songs-title').innerText = 'Songs (0/' + songs.size + ')';
|
||||
}
|
||||
|
||||
initData();
|
||||
updateQueue();
|
||||
setInterval(function () {
|
||||
updateQueue();
|
||||
}, 10000);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user