Browse Source

it works

master
lolcat 3 months ago
commit
abaaf79099
  1. 59
      README.md
  2. 744
      bot.js
  3. 7
      license.txt
  4. 16
      rena.conf

59
README.md

@ -0,0 +1,59 @@
# Rena Mumble
Welcome to the **only** working music bot for mumble (as of writing)! I made this cause the other scripts on github weren't working anymore, so here it is!
## Features
- NO web interface (who needs that shit?)
- It works, unlike the 45 other alternatives out there
## Installation
Okay, bare with me, I SWEAR this bot won't fail to install halfway trough.
First, make sure you install nodejs v.12.22.12 (or later) and npm 8.19.1 (or later!).
If your package manager doesn't give a recent enough version of nodejs, install it from here: https://nodejs.org/en/
From there, npm should be installed I think, if its not just install it trough your package manager and run `sudo npm install -g npm` to install the latest version.
Then, run these commands one by one:
```
sudo apt install ffmpeg git
git clone <this repo>.git
cd <the repo>
npm install noodle.js ytdl-core ytsr node-fetch sharp
```
Then, create a private key and certificate using the following command:
```
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem
```
This should create a key.pem and cert.pem file. Place those at the root of the project. Then, edit the rena.conf file to your liking!!
## Start the thing
```
nodejs bot.js
```
## Q&A
**Q: Sometimes, it just skips the video right away! What the fuck!!!!**
Check if your serber is blocked from youtube. The video you requested might also be age restricted, a livestream, or something else happened that makes streaming the video impossible.
**Q: Will, you lied to me nothing fucking works!!**
It's probably cause the youtube downloader script is fucked. Try updating it periodically with
```
Run npm install ytdl-core@latest
Run npm install ytsr@latest
```
**Q: What are the commands???**
Make sure you're located in the same channel as the bot and type `.help`. It should be smooth sailing from there!
**Q: I found a bug or a missing feature!**
Contact me: https://lolcat.ca/contact/
## Try it out
Theres a live instance of it running (hopefully) at mumble://lolcat.ca:64738 inside the music channel.
## License
MIT

744
bot.js

@ -0,0 +1,744 @@
const fs = require("fs");
const noodle = require("noodle.js");
const yt = require("ytdl-core");
const search = require("ytsr");
const fetch = require("node-fetch");
const sharp = require("sharp");
/*
Read config
*/
var config_file = fs.readFileSync("./rena.conf", "utf8");
config_file = config_file.split("\n");
config = {};
for(i=0; i<config_file.length; i++){
config_file[i] = config_file[i].trim();
if(
config_file[i] == "" ||
config_file[i][0] == "#"
){
continue;
}
var tmp = config_file[i].split("=");
switch(tmp[0]){
case "port":
tmp[1] = parseInt(tmp[1]);
break;
}
config[tmp[0]] = tmp[1];
}
const client = new noodle({
url: config.ip,
port: config.port,
name: config.name,
password: config.password,
key: fs.readFileSync(config.key),
cert: fs.readFileSync(config.cert)
});
/*
Variables
*/
var ratelimitbuffer = [];
var videochoosebuffer = [];
var playlist = [];
var playing = false;
var looping = false;
var volume = 1;
var download = null;
/*
Functions
*/
function htmlspecialchars(str){
return str.replace(/[&<>'"]/g,x=>'&#'+x.charCodeAt(0)+';')
}
async function play(){
if(playing){
client.voiceConnection.stopStream();
playing = false;
}
if(playlist.length === 0){
client.sendMessage("The queue is now empty!!");
download = null;
return;
}
client.sendMessage("Now playing: <b>" + playlist[0].title + "</b> requested by <b>" + playlist[0].requested_by + "</b>!!");
playing = true;
try{
download =
await yt(
playlist[0].url,
{
filter: "audioonly"
}
);
client.voiceConnection.playStream(download);
client.voiceConnection.setVolume(volume);
}catch(e){
error();
return;
}
download.once("error", error);
}
client.voiceConnection.on("end", function(){
playing = false;
if(!looping){
playlist.shift();
play();
return;
}
play();
return;
});
client.voiceConnection.on("error", error);
function error(){
client.sendMessage("Fuck!! An error occured while reading <b>" + playlist[0].title + "</b>!! Skipping...");
playlist.shift();
play();
return;
}
function spaceout(number, strlen){
number = number - strlen + 1;
var spaces = "";
for(k=0; k<number; k++){
spaces += " ";
}
return spaces;
}
function buildlist(items, offset = 0){
var msg = "<pre>";
var spacing = [];
var need_spacing = {
"offset": 0,
"duration": 0
};
if(typeof items[0].requested_by != "undefined"){
need_spacing.requested_by = 0;
}
for(k=0; k<items.length; k++){
for(var spaced in need_spacing){
if(spaced == "offset"){
var text = "#" + (k + offset);
}else{
var text = items[k][spaced];
}
if(text.length > need_spacing[spaced]){
need_spacing[spaced] = text.length;
}
}
}
for(i=0; i<items.length; i++){
msg +=
"<b>#" + (i + offset + 1) + "</b>" + spaceout(need_spacing.offset, (i + offset + 1).toString().length + 1) +
items[i].duration + spaceout(need_spacing.duration, items[i].duration.length);
if(typeof need_spacing.requested_by != "undefined"){
msg += "<b>" + items[i].requested_by + "</b>" + spaceout(need_spacing.requested_by, items[i].requested_by.length);
}
msg += "<a href=\"" + items[i].url + "\">" + items[i].title + "</a><br>";
}
msg += "</pre>";
return msg;
}
function s_to_hms(s) {
s = s * 1000;
var seconds = (s / 1000).toFixed(0);
var minutes = Math.floor(seconds / 60);
var hours = "";
if (minutes > 59){
hours = Math.floor(minutes / 60);
minutes = minutes - (hours * 60);
}
seconds = Math.floor(seconds % 60);
if (hours != ""){
return hours + ":" + minutes + ":" + seconds;
}
return minutes + ":" + seconds;
}
client.on("ready", function(){
console.log("Ready to kick ass");
if(config.channel.match(/^[0-9]+$/)){
config.channel = parseInt(config.channel);
}else{
config.channel = client.channels.find("name", config.channel).id
}
client.switchChannel(config.channel);
});
client.on("message", async function(message){
var message_content = message.content.replace(/<.*?>/g, "");
matches = message_content.match(/^\.([^ ]+)(?: (.*))?/);
if(matches === null){ return; }
[undefined, cmd, value] = matches;
if(typeof value == "undefined"){ value = ""; }
value = value.trim();
/*
Commands
*/
var ratelimit = 0;
// ratelimit
switch(cmd){
case "ping":
ratelimit = 5;
break;
case "help":
case "h":
cmd = "help";
ratelimit = 5;
break;
case "play":
case "p":
case "search":
cmd = "play";
ratelimit = 2;
break;
case "loop":
case "l":
cmd = "loop";
ratelimit = 2;
break;
case "skip":
case "s":
cmd = "skip";
ratelimit = 2;
break;
case "np":
case "n":
case "nowplaying":
cmd = "nowplaying";
ratelimit = 4;
break;
case "volume":
case "vol":
case "v":
cmd = "volume";
ratelimit = 2;
break;
case "queue":
case "list":
case "q":
cmd = "queue";
ratelimit = 2;
break;
default:
// dont store in ratelimit
return;
}
/*
Ratelimit
*/
if(typeof ratelimitbuffer[message.sender.hash] == "undefined"){
ratelimitbuffer[message.sender.hash] = [];
}
// check
for(i=0; i<ratelimitbuffer[message.sender.hash].length; i++){
if(ratelimitbuffer[message.sender.hash][i] == cmd){
client.sendMessage("You're being ratelimited, dumbass!!");
return;
}
}
// add
ratelimitbuffer[message.sender.hash].push(cmd);
// remove after x seconds
var timeout = setTimeout(function(arg){
for(i=0; i<ratelimitbuffer[arg[0]].length; i++){
if(ratelimitbuffer[arg[0]][i] == arg[1]){
ratelimitbuffer[arg[0]].splice(i, 1);
if(ratelimitbuffer[arg[0]].length === 0){
delete ratelimitbuffer[arg[0]];
}
return;
}
}
}, ratelimit * 1000, [message.sender.hash, cmd]);
switch(cmd){
case "ping":
client.sendMessage("Pong!!!");
break;
case "play":
if(value == ""){
client.sendMessage("You need to search for something, retard!!");
return;
}
if(yt.validateURL(value)){
var id = yt.getURLVideoID(value);
try{
var dlinfo = await yt.getInfo("https://www.youtube.com/watch?v=" + id);
var thumb = "";
var high = 0;
for(i=0; i<dlinfo.player_response.videoDetails.thumbnail.thumbnails.length; i++){
if(
dlinfo.player_response.videoDetails.thumbnail.thumbnails[i].width
> high
){
high = dlinfo.player_response.videoDetails.thumbnail.thumbnails[i].width;
thumb = dlinfo.player_response.videoDetails.thumbnail.thumbnails[i].url;
}
}
if(
dlinfo.player_response.videoDetails.lengthSeconds === null ||
dlinfo.player_response.videoDetails.isLiveContent === true ||
dlinfo.player_response.videoDetails.isPrivate === true
){
client.sendMessage("Could not read <b>" + value + "</b>, your video probably sucked!!");
return;
}
playlist.push({
title: htmlspecialchars(dlinfo.player_response.videoDetails.title),
url: "https://www.youtube.com/watch?v=" + id,
duration: s_to_hms(parseInt(dlinfo.player_response.videoDetails.lengthSeconds)),
thumb: thumb,
author_name: htmlspecialchars(dlinfo.player_response.microformat.playerMicroformatRenderer.ownerChannelName),
author_url: dlinfo.player_response.microformat.playerMicroformatRenderer.ownerProfileUrl,
requested_by: message.sender.name
});
if(playlist.length !== 1){
client.sendMessage("Added <b>" + playlist[0].title + "</b> to the queue!!");
}else{
download = await yt.downloadFromInfo(dlinfo, {filter: "audioonly"})
play();
}
}catch(e){
client.sendMessage("Could not read <b>" + value + "</b>, your video probably sucked!!");
}
return;
}
if(value.match(/^[0-9]+$/)){
value = parseInt(value) - 1;
/*
Check search buffer
*/
if(
typeof videochoosebuffer[message.sender.hash] != "undefined" &&
value >= 0 &&
value < videochoosebuffer[message.sender.hash]["results"].length
){
// store in playlist
videochoosebuffer[message.sender.hash]["results"][value].requested_by = message.sender.name;
playlist.push(videochoosebuffer[message.sender.hash]["results"][value]);
if(playlist.length !== 1){
client.sendMessage("Added <b>" + videochoosebuffer[message.sender.hash]["results"][value].title + "</b> to the queue!!");
}else{
await play();
}
}else{
if(typeof videochoosebuffer[message.sender.hash] == "undefined"){
client.sendMessage("You need to search for something first, dickhead!!");
return;
}
client.sendMessage("Stupid american!!!! Enter walid numbwer!!!");
}
return;
}
/*
Search youtube
*/
try{
var results_raw = await search(value, {pages: 1});
}catch(e){
client.sendMessage("Failed to fetch results from youtube! Is this IP banned?");
return;
}
var results = [];
// filter out results
for(i=0; i<results_raw.items.length; i++){
if(
results_raw.items[i].type != "video" ||
results_raw.items[i].isLive ||
results_raw.items[i].isUpcoming ||
results_raw.items[i].duration === null
){
continue;
}
results.push({
title: htmlspecialchars(results_raw.items[i].title),
url: results_raw.items[i].url,
duration: results_raw.items[i].duration,
thumb: results_raw.items[i].bestThumbnail.url,
author_name: htmlspecialchars(results_raw.items[i].author.name),
author_url: results_raw.items[i].author.url
});
if(results.length === 8){ break; }
}
// store results for later use
if(typeof videochoosebuffer[message.sender.hash] == "undefined"){
// init videochoosebuffer for user
videochoosebuffer[message.sender.hash] = [];
// remove results from mem after 2 minutes
videochoosebuffer[message.sender.hash]["function"] =
setTimeout(function(hash){
delete videochoosebuffer[hash];
}, 120000, message.sender.hash);
}else{
// a timeout was set, remove it
clearTimeout(videochoosebuffer[message.sender.hash]["function"]);
}
// finally, store the results
videochoosebuffer[message.sender.hash]["results"] = results;
if(results.length === 0){
client.sendMessage("<h3>No results for <u>" + htmlspecialchars(results_raw.correctedQuery) + "</u></h3>");
return;
}
var msg =
"<h3>Results for <u>" +
htmlspecialchars(results_raw.correctedQuery) +
"</u>:</h3>Choose a video with <i>.play &lt;1-" +
results.length +
"&gt;</i>";
msg += buildlist(results);
client.sendMessage(msg);
break;
case "queue":
if(playlist.length === 0){
client.sendMessage("The queue is empty fatass!!");
return;
}
value = parseInt(value);
if(isNaN(value)){
value = 1;
}
pagelen = Math.ceil(playlist.length / 8);
if(
value <= 0 ||
value > pagelen
){
client.sendMessage("Yow stupid american!! Enter numbwer between 1 and " + pagelen + "!!");
return;
}
var msg =
"<h3>Playlist!!</h3>" +
"Page " + value + " of " + pagelen;
var offset = (value - 1) * 8;
var offset_end = offset + 8;
if(offset_end >= playlist.length){
offset_end = playlist.length;
}
var playlist_truncated = [];
for(i=offset; i<offset_end; i++){
playlist_truncated.push(playlist[i]);
}
msg += buildlist(playlist_truncated, offset);
client.sendMessage(msg);
break;
case "loop":
if(looping){
looping = false;
client.sendMessage("No longer looping songs!!");
return;
}
looping = true;
client.sendMessage("Songs will now loop!!");
break;
case "skip":
if(playlist.length === 0){
client.sendMessage("Theres nothing to skip you deaf motherfucker!!");
return;
}
if(value == ""){
var id = 1;
}else{
if(value.match(/^[0-9]+$/)){
var id = parseInt(value);
}else{
// skip user's submissions
var oldvid = playlist[0].url;
var newlist = [];
for(i=0; i<playlist.length; i++){
if(playlist[i].requested_by != value){
newlist.push(playlist[i]);
}
}
client.sendMessage("Skipped <b>" + (playlist.length - newlist.length) + "</b> videos requested by <b>" + value + "</b>!!");
playlist = newlist;
if(
playlist.length === 0 ||
oldvid != playlist[0].url
){
play();
}
return;
}
}
id = id - 1;
if(
id <= -1 ||
id >= playlist.length
){
client.sendMessage("Stupid american!!! Specify numbwer between <b>1</b> and <b>" + playlist.length + "</b>!!");
return;
}
client.sendMessage("Skipped <b>" + playlist[id].title + "</b> caused it sucked big hairy balls!!");
playlist.splice(id, 1);
if(id === 0){
play();
}
break;
case "clear":
if(playlist.length === 0){
client.sendMessage("The playlist is already cleared you moron");
return;
}
playlist = [];
play();
break;
case "volume":
value = parseInt(value);
if(isNaN(value)){
client.sendMessage("Enter a valid number you stupid bitch!!");
return;
}
if(value > 100){ value = 100; }
if(value < 0){ value = 0; }
volume = value / 100;
if(playing){
client.voiceConnection.setVolume(volume);
}
client.sendMessage("Changed the volume to <b>" + value + "%</b>!!");
break;
case "nowplaying":
if(playlist.length === 0){
client.sendMessage("Theres nothing playing now you deaf fuck!!");
return;
}
var thumb = await fetch(playlist[0].thumb);
thumb = await thumb.buffer();
thumb = await sharp(thumb)
.resize(180)
.jpeg({
mozjpeg: true,
quality: 20,
effort: 10
})
.toBuffer();
client.sendMessage(
"<h3>Now playing:</h3>" +
"[" + playlist[0].duration + "] " +
"<b><a href=\"" + playlist[0].url + "\">" + playlist[0].title + "</a></b> by " +
"<b><a href=\"" + playlist[0].author_url + "\">" + playlist[0].author_name + "</a></b><br>" +
"Requested by: <b>" + playlist[0].requested_by + "</b><br>" +
"<img src=\"data:image/jpeg;base64," + thumb.toString("base64") + "\">"
);
break;
case "help":
client.sendMessage(
"<h3>Rena's awesum halp menu!!11</h3><pre>" +
".play &lt;search|url|choice&gt; <i>Play a youtube video</i><br>" +
".volume &lt;0-100&gt; <i>Set the volume ecks dee</i><br>" +
".queue &lt;page&gt; <i>View all songs added so far</i><br>" +
".skip &lt;song ID|user&gt; <i>ID is the song position in .queue</i><br>" +
".clear <i>Cleanse the queue from all demons</i><br>" +
".loop <i>Endless torment</i><br>" +
".nowplaying <i>See what atrocity you're listening to</i><br>" +
".ping <i>pong ching chong wing wong</i><br>" +
".help <i>Yoo idk what this one does</i></pre>" +
// pls no remove xoxo
"Rena Chan is brought to you by <a href=\"https://lolcat.ca\">lolcat</a>. Email me if shit hits the fan: <a href=\"mailto:will@lolcat.ca\">will@lolcat.ca</a>"
);
break;
}
});
client.connect();

7
license.txt

@ -0,0 +1,7 @@
Copyright © 2022 lolcat (https://lolcat.ca)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

16
rena.conf

@ -0,0 +1,16 @@
# Rena mumble config file
ip=lolcat.ca
port=64738
name=rena_chan
password=
# Certificate location
key=./key.pem
cert=./cert.pem
# Make sure the channel has these permissions for @all:
# traverse, enter, speak, text message. enter permission
# is not needed if you manually drop Rena in the wanted
# channel. Specify channel name or channel ID
channel=music
Loading…
Cancel
Save