Compare commits

..

17 Commits

Author SHA1 Message Date
Christian Richter
e54c6e8b72 reenable amd64
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-10-16 17:55:35 +02:00
Christian Richter
b9f505a64e disable amd64 builds
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build was killed
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-08-29 10:44:30 +02:00
Christian Richter
904a96dcbb fix missing s in yt api link
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-08-01 08:33:09 +02:00
Christian Richter
aaf3b71b7c fix git url
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-30 16:56:53 +02:00
Christian Richter
4840619908 minor cleanups
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build was killed
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-30 16:46:59 +02:00
Christian Richter
8bc0539032 update layout, extract js code & css from html
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-30 12:50:28 +02:00
Christian Richter
4510449225 next attempt
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 11:55:52 +02:00
Christian Richter
cb696853f5 try to fix broken remove song
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 11:40:36 +02:00
Christian Richter
fa5216c8d4 add secure protocol
Some checks reported errors
continuous-integration/drone/pr Build was killed
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 11:19:05 +02:00
Christian Richter
4e13c78c5a test cross origin for websocket domain
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 10:08:58 +02:00
Christian Richter
a023fbed87 add listener count to ws message
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 09:03:37 +02:00
Christian Richter
27916b6356 make domains configurable & extend webserver
Some checks reported errors
continuous-integration/drone/pr Build was killed
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-19 09:01:41 +02:00
Christian Richter
bc030bb121 fix broken badge
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-18 15:53:36 +02:00
Christian Richter
6b1dd40c92 fix wrong branches
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-18 15:53:36 +02:00
Christian Richter
d6e26e739f update readme
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-18 15:53:36 +02:00
d1b67d52fe Merge pull request 'update readme' (#1) from add-readme into main
Reviewed-on: #1
2022-07-18 15:31:44 +02:00
Christian Richter
7d60c306c0 update readme
Some checks reported errors
continuous-integration/drone/pr Build was killed
Signed-off-by: Christian Richter <crichter@owncloud.com>
2022-07-18 15:31:10 +02:00
9 changed files with 285 additions and 201 deletions

View File

@@ -2,8 +2,8 @@ def main(ctx):
return [ return [
stepPR("amd64", "weirdradio"), stepPR("amd64", "weirdradio"),
stepPR("arm64", "weirdradio"), stepPR("arm64", "weirdradio"),
stepMergeMaster("amd64", "weirdradio"), stepMergeMain("amd64", "weirdradio"),
stepMergeMaster("arm64", "weirdradio"), stepMergeMain("arm64", "weirdradio"),
stepBuildWeekly("amd64", "weirdradio"), stepBuildWeekly("amd64", "weirdradio"),
stepBuildWeekly("arm64", "weirdradio"), stepBuildWeekly("arm64", "weirdradio"),
@@ -51,7 +51,7 @@ def notify(ctx):
], ],
"trigger": { "trigger": {
"ref": [ "ref": [
"refs/heads/master", "refs/heads/main",
"refs/heads/release*", "refs/heads/release*",
"refs/tags/**", "refs/tags/**",
"refs/pull/**", "refs/pull/**",
@@ -95,7 +95,7 @@ def stepPR(arch, path):
}, },
} }
def stepMergeMaster(arch, path): def stepMergeMain(arch, path):
return { return {
"kind": "pipeline", "kind": "pipeline",
"type": "docker", "type": "docker",
@@ -124,7 +124,7 @@ def stepMergeMaster(arch, path):
], ],
"trigger": { "trigger": {
"ref": [ "ref": [
"refs/heads/master", "refs/heads/main",
], ],
"status": [ "status": [
"success", "success",
@@ -162,7 +162,7 @@ def stepBuildWeekly(arch, path):
], ],
"trigger": { "trigger": {
"ref": [ "ref": [
"refs/heads/master", "refs/heads/main",
], ],
"event": [ "event": [
"cron" "cron"

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
**/*.json **/*.json
node_modules node_modules
.drone.yml .drone.yml
assets/baseurl.js

View File

@@ -1,6 +1,6 @@
FROM node:latest FROM node:latest
LABEL maintainer="dragonchaser <weirdradio@datenschmutz.space>" LABEL maintainer="dragonchaser <weirdradio@datenschmutz.space>"
RUN git clone https://github.com/dragonchaser/weirdradio /weirdradio RUN git clone https://git.wardrum.de/dragonchaser/weirdradio /weirdradio
WORKDIR /weirdradio WORKDIR /weirdradio
RUN npm install RUN npm install
CMD node weirdradio CMD node weirdradio

View File

@@ -1,11 +1,12 @@
[![Build Status](https://drone.services.datenschmutz.space/api/badges/dragonchaser/weirdradio/status.svg)](https://drone.services.datenschmutz.space/dr [![Build Status](https://drone.services.datenschmutz.space/api/badges/dragonchaser/weirdradio/status.svg)](https://drone.services.datenschmutz.space/dragonchaser/weirdradio)
# Weirdradio # Weirdradio
Weirdradio is a matrix bot that will queue youtube videos into a webfrontend for listening. Weirdradio is a matrix bot that will queue youtube videos into a webfrontend for listening.
This can be used as party juke box (especially in combination with the whatsapp or signal bridge). This can be used as party juke box (especially in combination with the whatsapp or signal bridge).
**NOTE:** The software is WIP, expect breaking changes, weird behaviour and gargoyle spitting wormholes!
## License ## License
MIT see [LICENSE](https://github.com/dragonchaser/matrix-feeder/blob/master/LICENSE) file in this repository. MIT see [LICENSE](https://github.com/dragonchaser/matrix-feeder/blob/master/LICENSE) file in this repository.
@@ -13,8 +14,8 @@ MIT see [LICENSE](https://github.com/dragonchaser/matrix-feeder/blob/master/LICE
## Run ## Run
``` ```
docker build . -t local/weirdradio:latest $> docker build . -t local/weirdradio:latest
docker run -it \ $> docker run -it \
-p8090:8090 \ -p8090:8090 \
-p8080:8080 \ -p8080:8080 \
-v $(pwd)/config:/weirdradio/config \ -v $(pwd)/config:/weirdradio/config \

View File

@@ -2,200 +2,28 @@
<head> <head>
<script <script
type="text/javascript" type="text/javascript"
src="http://www.youtube.com/player_api" src="https://www.youtube.com/player_api"
></script> ></script>
<script type="text/javascript" src="baseurl.js"></script>
<script type="text/javascript" src="logic.js"></script>
<link rel="stylesheet" href="style.css" />
<title>Weirdradio</title> <title>Weirdradio</title>
</head> </head>
<style>
div.body {
width: 1024px;
height: 768px;
border: solid 1px;
padding: 1pc;
}
div.leftcolumn {
width: 30%;
float: left;
}
div.title {
height: 20%;
}
div.playlist {
height: 80%;
}
div.rightcolumn {
width: 60%;
float: left;
}
#playerframe {
height: 100%;
width: 100%;
background-color: purple;
}
</style>
<body onload="connectSocket()"> <body onload="connectSocket()">
<div class="body"> <div class="body">
<div class="leftcolumn">
<div class="title"> <div class="title">
<h1>Weirdradio</h1> Weirdradio
</div> </div>
<div id="playlist" class="playlist"></div> <div class="listeners">Listeners: <span id="listeners">1</span></div>
</div>
<div class="rightcolumn">
<div class="playerframe"> <div class="playerframe">
<div id="player"></div> <div id="player"></div>
</div> </div>
<div class="playlistframe">
<div class="title">
Playlist
</div>
<div id="playlist" class="playlist"></div>
</div> </div>
</div> </div>
</body> </body>
<script>
var playlist = [];
var socketConnector = null;
var socketSemaphore = false;
var tag = document.createElement("script");
var currentVideoID = null;
var player = null;
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
currentVideoID = "Wch3gJG2GJ4";
player = new YT.Player("player", {
height: "390",
width: "640",
videoId: currentVideoID,
playerVars: {
playsinline: 1,
},
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
},
});
}
function onPlayerReady(event) {
event.target.playVideo();
}
var done = false;
function onPlayerStateChange(event) {
if (event.data == 0) {
console.log("playing stopped");
removeFromPlaylist(currentVideoID);
playNext();
}
//if (event.data == YT.PlayerState.PLAYING && !done) {
// setTimeout(stopVideo, 6000);
// done = true;
//}
}
function stopVideo() {
player.stopVideo();
}
function connectSocket() {
if (socketSemaphore) {
console.log(
"Another connection attempt already in progress, canceling!"
);
return;
}
console.log("Connecting to socket");
socketSemaphore = true;
baseUrl = window.location.host.replace("8080", "8090");
let socket = new WebSocket("ws://" + baseUrl);
socket.onopen = function (e) {
clearInterval(socketConnector);
socketConnector = null;
socketSemaphore = false;
console.log("[open] Connection established");
};
// TODO: on close and on error attempt reconnect
socket.onclose = function (event) {
if (event.wasClean) {
console.log(
`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`
);
} else {
// e.g. server process killed or network down
// event.code is usually 1006 in this case
// TODO: add some nice ui thingi to show reconnect
// TODO: also add some timeout function to repeat this until connect is successful, let it interact with 'onopen'
console.log("[close] Connection died, attempting reconnect");
if (!socketConnector) {
socketConnector = setInterval(connectSocket, 5000);
socketSemaphore = false;
}
}
};
socket.onmessage = function (event) {
data = JSON.parse(event.data);
if (data["videoId"] != undefined && data["videoId"] != null) {
addToPlaylist(data);
console.log(`[message] Data received from server: ${event.data}`);
} else {
console.log(
`[unparseable message] Data received from server ${event.data}`
);
}
};
socketSemaphore = false;
}
function removeFromPlaylist(videoId) {
tmp = [];
playlist.forEach((item) => {
if (item.videoId != videoId) {
tmp.push(item);
} else {
console.log(`removing ${videoId}`);
}
});
playlist = tmp;
// remove from DOM
rm = document.getElementById(videoId);
if (rm != null) {
rm.remove();
}
currentVideoID = null;
}
function playNext() {
console.log(playlist)
console.log(playlist[0].videoId)
if (playlist[0] != null && playlist[0] != "") {
player.loadVideoById(playlist[0].videoId);
player.playVideo();
}
}
function addToPlaylist(data) {
if (document.getElementById(data.videoId) == null) {
playlist.push(data);
pl = document.getElementById(
"playlist"
).innerHTML += `<div id="${data.videoId}"><div>${data.title}</div><div><a target="_blank" href="${data.link}">${data.link}</a></div></div>`;
if (player.getPlayerState() == YT.PlayerState.ENDED) {
player.loadVideoById(data["videoId"]);
player.playVideo();
currentVideoID = data["videoId"];
}
} else {
console.log(`item ${data.videoId} already exists in playlist skipping`);
}
}
//function myDebugger() {
// console.log(socketSemaphore);
// console.log(socketConnector);
// console.log("=================");
//}
//myDebugger = setInterval(myDebugger, 2000);
</script>
</html> </html>

142
assets/logic.js Normal file
View File

@@ -0,0 +1,142 @@
var playlist = [];
var socketConnector = null;
var socketSemaphore = false;
var tag = document.createElement("script");
var currentVideoID = null;
var player = null;
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName("script")[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
currentVideoID = "tbnLqRW9Ef0";
player = new YT.Player("player", {
//height: "390",
//width: "100%",
videoId: currentVideoID,
playerVars: {
playsinline: 1,
},
events: {
onReady: onPlayerReady,
onStateChange: onPlayerStateChange,
},
});
}
function onPlayerReady(event) {
event.target.playVideo();
}
var done = false;
function onPlayerStateChange(event) {
if (event.data == 0) {
playNext();
console.log("playing stopped");
}
//if (event.data == YT.PlayerState.PLAYING && !done) {
// setTimeout(stopVideo, 6000);
// done = true;
//}
}
function stopVideo() {
player.stopVideo();
}
function connectSocket() {
if (socketSemaphore) {
console.log("Another connection attempt already in progress, canceling!");
return;
}
console.log("Connecting to socket");
socketSemaphore = true;
let socket = new WebSocket(wsBaseUrl);
socket.onopen = function (e) {
clearInterval(socketConnector);
socketConnector = null;
socketSemaphore = false;
console.log("[open] Connection established");
};
// TODO: on close and on error attempt reconnect
socket.onclose = function (event) {
if (event.wasClean) {
console.log(
`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`
);
} else {
// e.g. server process killed or network down
// event.code is usually 1006 in this case
// TODO: add some nice ui thingi to show reconnect
// TODO: also add some timeout function to repeat this until connect is successful, let it interact with 'onopen'
console.log("[close] Connection died, attempting reconnect");
if (!socketConnector) {
socketConnector = setInterval(connectSocket, 5000);
socketSemaphore = false;
}
}
};
socket.onmessage = function (event) {
data = JSON.parse(event.data);
if (data["videoId"] != undefined && data["videoId"] != null) {
addToPlaylist(data);
updateListeners(data);
console.log(`[message] Data received from server: ${event.data}`);
} else {
console.log(
`[unparseable message] Data received from server ${event.data}`
);
}
};
socketSemaphore = false;
}
function updateListeners(data) {
document.getElementById("listeners").innerHTML = data.listeners;
}
function removeFromPlaylist(videoId) {
delete playlist[currentVideoID];
// remove from DOM
rm = document.getElementById(videoId);
if (rm != null) {
rm.remove();
}
currentVideoID = null;
}
function playNext() {
removeFromPlaylist(currentVideoID);
if (playlist[0] != null && playlist[0] != "") {
player.loadVideoById(playlist[0].videoId);
player.playVideo();
}
}
function addToPlaylist(data) {
if (document.getElementById(data.videoId) == null) {
playlist.push(data);
pl = document.getElementById(
"playlist"
).innerHTML += `<div class="playlistrow" id="${data.videoId}">
<div class="title">${data.title}</div>
<div class="ytlink">
<a target="_blank" href="${data.link}">${data.link}</a>
</div>
<div class="controls">
<div class="control" onclick="removeFromPlaylist('${data.videoId}')">Remove</div>
<div class="control">Play</div>
</div>
</div>`;
if (player.getPlayerState() == YT.PlayerState.ENDED) {
player.loadVideoById(data["videoId"]);
player.playVideo();
currentVideoID = data["videoId"];
}
} else {
console.log(`item ${data.videoId} already exists in playlist skipping`);
}
}

79
assets/style.css Normal file
View File

@@ -0,0 +1,79 @@
body {
padding: 0;
margin: 0;
}
div.body {
position: relative;
width: 100%;
padding: 0;
margin: 0;
}
div.body > div.title {
padding: 1pc;
font-size: 2pc;
font-weight: bold;
float: left;
width: 50%;
}
div.body > div.listeners {
float: right;
width: 20%;
padding: 2pc 2pc 0 0;
font-size: xx-small;
font-weight: bold;
text-align: right;
}
div.playlistframe {
}
div.playlistframe > div.title {
padding: 1pc;
font-size: 1.5pc;
font-weight: bold;
}
div.playlist {
padding: 0;
}
div.playlist > div.playlistrow {
padding: 1pc;
position: relative;
}
div.playlist > div.playlistrow:nth-child(even) {
background: #fff;
}
div.playlist > div.playlistrow:nth-child(odd) {
background: #ddd;
}
div.playlist > div.playlistrow > div.title{
font-weight: bold;
font-size: medium;
}
div.playlist > div.playlistrow > div.ytlink{
font-size: xx-small;
}
div.playlist > div.playlistrow > div.controls {
position: absolute;
top: 0;
right: 0;
}
div.playlist > div.playlistrow > div.controls > div.control {
float: right;
background-color: #aaa;
margin: 1pc 0.5pc 0 0;
cursor: pointer;
}
div.playerframe {
}
#player {
width: 100%;
}

View File

@@ -1,6 +1,9 @@
{ {
"homeServerUrl": "https://matrix.your-homeserver.org", "homeServerUrl": "https://matrix.your-homeserver.org",
"accessToken": "youraccesstokencanbeobtainedthroughriot", "accessToken": "youraccesstokencanbeobtainedthroughriot",
"domain": "yourdomain.tld",
"webSocketDomain": "ws.yourdomain.tld",
"secure":true,
"storage": "config/bot.json", "storage": "config/bot.json",
"assetDir": "assets/", "assetDir": "assets/",
"webServerPort": "8080", "webServerPort": "8080",

View File

@@ -20,14 +20,27 @@ const client = new MatrixClient(
new SimpleFsStorageProvider(config.storage) new SimpleFsStorageProvider(config.storage)
); );
// write javascript for baseurl
if (config.secure) {
urlConfigData = `var baseUrl = "https://${config.domain}";\nvar wsBaseUrl = "wss://${config.webSocketDomain}";\n`;
} else {
urlConfigData = `var baseUrl = "http://${config.domain}";\nvar wsBaseUrl = "ws://${config.webSocketDomain}";\n`;
}
fs.writeFileSync(
config.assetDir + "/baseurl.js",
urlConfigData,
function (err) {
if (err) return console.log(err);
}
);
// load assets // load assets
console.log("Reading assets...") console.log("Reading assets...");
let assets = []; let assets = [];
var files = fs.readdirSync(config.assetDir); var files = fs.readdirSync(config.assetDir);
files.forEach((name) => { files.forEach((name) => {
assets[name] = fs.readFileSync("assets/" + name); assets[name] = fs.readFileSync("assets/" + name);
}); });
console.log("[DONE]") console.log("[DONE]");
// create server, this is for delivering the iframe page // create server, this is for delivering the iframe page
const webServer = http const webServer = http
.createServer((req, res) => { .createServer((req, res) => {
@@ -36,7 +49,23 @@ const webServer = http
reqFileName = "index.html"; reqFileName = "index.html";
} }
if (assets[reqFileName] != undefined && assets[reqFileName] != null) { if (assets[reqFileName] != undefined && assets[reqFileName] != null) {
res.writeHead(200, { "Content-Type": "text/html" }); extension = reqFileName.split(".").pop();
if (extension == "html" || extension == "html") {
res.writeHead(200, {
"Content-Type": "text/html",
"Access-Control-Allow-Origin": "https://" + config.webSocketDomain,
});
} else if (extension == "js") {
res.writeHead(200, {
"Content-Type": "text/javascript",
"Access-Control-Allow-Origin": "https://" + config.webSocketDomain,
});
} else {
res.writeHead(200, {
"Content-TYpe": "text/plain",
"Access-Control-Allow-Origin": "https://" + config.webSocketDomain,
});
}
res.end(assets[reqFileName]); res.end(assets[reqFileName]);
} else { } else {
res.writeHead(404, { "Content-Type": "text/plain" }); res.writeHead(404, { "Content-Type": "text/plain" });
@@ -85,7 +114,7 @@ client.on("room.message", (roomId, event) => {
//link_matches = body.match(/https?:\/\/[^\ ]*youtu[^\ ]*/g); //link_matches = body.match(/https?:\/\/[^\ ]*youtu[^\ ]*/g);
var r = new RegExp(/https?:\/\/[^\ ]*youtube.com\/watch\?v=([^\ ]*)/g); var r = new RegExp(/https?:\/\/[^\ ]*youtube.com\/watch\?v=([^\ ]*)/g);
link_matches = r.exec(body); link_matches = r.exec(body);
if (link_matches && link_matches.length > 1) { if (link_matches && link_matches.length > 1 && link_matches[0] != null) {
// get title // get title
request(link_matches[0], function (err, _res, body) { request(link_matches[0], function (err, _res, body) {
if (err) return console.error(err); if (err) return console.error(err);
@@ -100,6 +129,7 @@ client.on("room.message", (roomId, event) => {
link: link_matches[0], link: link_matches[0],
videoId: link_matches[1], videoId: link_matches[1],
title: title, title: title,
listeners: sockets.length,
}; };
console.log("Relaying: " + obj.link); console.log("Relaying: " + obj.link);
sockets.forEach((s) => s.send(JSON.stringify(obj))); sockets.forEach((s) => s.send(JSON.stringify(obj)));