add code from codecat

This commit is contained in:
deflax 2022-03-22 20:25:39 -04:00
parent f0607b82e7
commit 0b8f9210d3
51 changed files with 3639 additions and 0 deletions

9
Dockerfile Normal file
View file

@ -0,0 +1,9 @@
FROM codecatt/reddit-radio:base
ENV CONFIG_FILE="./config/config.toml"
WORKDIR /app
COPY . /app
CMD ["node", "index.js"]

18
Dockerfile.base Normal file
View file

@ -0,0 +1,18 @@
FROM node:16-alpine
WORKDIR /app
COPY ./package.json /app/package.json
RUN apk add --no-cache ffmpeg \
&& apk add --no-cache --virtual .build-deps \
g++ \
gcc \
libgcc \
make \
autoconf \
libtool \
automake \
python3 \
&& npm install \
&& apk del .build-deps

118
Radio.js Normal file
View file

@ -0,0 +1,118 @@
var discord = require("discord.js");
class Radio
{
constructor(config, radioconfig)
{
this.config = config;
this.name = radioconfig.name;
this.url = radioconfig.url;
this.client = new discord.Client({
intents: [
// List of intents: https://discord.com/developers/docs/topics/gateway#list-of-intents
discord.Intents.FLAGS.GUILDS,
discord.Intents.FLAGS.GUILD_VOICE_STATES,
],
});
this.running = false;
this.voice_connection = false;
this.voice_dispatcher = false;
this.channel = false;
this.client.on("ready", () => {
console.log("Radio client \"" + this.name + "\" connected!");
this.client.channels.fetch(radioconfig.channel).then(channel => {
this.channel = channel;
if (channel.members.size > 0) {
this.joinChannel();
}
});
});
this.client.on("error", (e) => {
console.log("Radio bot error:", e);
});
this.client.on("voiceStateUpdate", (o, n) => {
if (n.channel == this.channel) {
console.log("Someone joined \"" + this.name + "\": " + this.channel.members.size);
if (!this.running) {
this.running = true;
this.joinChannel();
}
} else if (o.channel == this.channel && n.channel != this.channel) {
console.log("Someone left \"" + this.name + "\": " + this.channel.members.size);
if (this.running && this.channel.members.size == 1) {
this.running = false;
this.leaveChannel();
}
}
});
this.client.login(radioconfig.token);
}
stop()
{
console.log("Stopping radio \"" + this.name + "\"...");
return this.client.destroy();
}
joinChannel()
{
console.log("Joining and starting \"" + this.name + "\"!");
this.running = true;
this.channel.join().then((conn) => {
this.voice_connection = conn;
this.voice_connection.on("disconnect", () => {
this.voice_connection = false;
});
this.voice_connection.on("newSession", () => {
this.startBroadcast();
});
this.voice_connection.on("error", (err) => {
console.log("Error: " + err);
});
this.startBroadcast();
}).catch(console.error);
}
leaveChannel()
{
console.log("Leaving \"" + this.name + "\".");
this.running = false;
if (this.voice_dispatcher !== false) {
this.voice_dispatcher.end();
}
if (this.voice_connection !== false) {
this.voice_connection.disconnect();
}
}
startBroadcast()
{
if (this.voice_connection === false) {
return;
}
if (this.voice_dispatcher !== false) {
this.voice_dispatcher.end();
}
this.voice_dispatcher = this.voice_connection.play(this.url, this.config.voice);
this.voice_dispatcher.on("end", (reason) => {
console.log("Radio voice \"" + this.name + "\" ended: \"" + reason + "\"");
this.voice_dispatcher = false;
});
}
}
module.exports = Radio;

445
RedditRadio.js Normal file
View file

@ -0,0 +1,445 @@
var discord = require("discord.js");
var colors = require("colors");
var moment = require("moment-timezone");
var process = require("process");
var fs = require("fs");
var https = require("https");
var cmdsplit = require("./cmdsplit");
var Radio = require("./Radio");
var MongoClient = require("mongodb").MongoClient;
function findCommand(obj, cmdID)
{
var cmdName = "onCmd" + cmdID;
var cmdRegex = new RegExp("^" + cmdName + "$", "i");
var prototype = Object.getPrototypeOf(obj);
var props = Object.getOwnPropertyNames(prototype);
for (var i = 0; i < props.length; i++) {
var key = props[i];
if (!key.startsWith("onCmd")) {
continue;
}
if (key.match(cmdRegex)) {
return obj[key];
}
}
return null;
}
class RedditRadio
{
constructor(config)
{
this.config = config;
this.readyPromises = [];
moment.tz.setDefault(this.config.discord.timezone || "Europe/Amsterdam");
console.log('Discord.js version', discord.version);
this.client = new discord.Client({
intents: [
// List of intents: https://discord.com/developers/docs/topics/gateway#list-of-intents
discord.Intents.FLAGS.GUILDS,
discord.Intents.FLAGS.GUILD_MEMBERS,
discord.Intents.FLAGS.GUILD_BANS,
discord.Intents.FLAGS.GUILD_MESSAGES,
discord.Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
discord.Intents.FLAGS.DIRECT_MESSAGES,
],
});
this.client.on("messageCreate", (msg) => { this.onMessage(msg, false); });
this.client.on("messageUpdate", (oldMsg, newMsg) => { this.onMessageUpdate(oldMsg, newMsg); });
this.client.on("messageDelete", (msg) => { this.onMessageDelete(msg); });
this.client.on("guildMemberAdd", (member) => { this.onMemberJoin(member); });
this.readyPromises.push(this.client.login(this.config.discord.token));
this.modules = [];
if (this.config.database) {
this.mongoclient = new MongoClient(this.config.database.url, { useUnifiedTopology: true });
this.readyPromises.push(this.mongoclient.connect());
}
/** @type {discord.TextChannel} */
this.logChannel = null;
/** @type {discord.TextChannel} */
this.dmChannel = null;
/** @type {discord.TextChannel} */
this.errorChannel = null;
}
loadConfigModules()
{
if (!this.config.modules) {
return;
}
for (var name in this.config.modules) {
var moduleClass = require('./modules/' + name);
if (!moduleClass) {
console.error('Unable to find module with name "' + name + '"!');
continue;
}
var configs = this.config.modules[name];
console.log('Module: "' + name + '" (' + configs.length + ' instances)');
for (let config of configs) {
var newModule = new moduleClass(config, this.client, this);
this.modules.push(newModule);
}
}
}
onReady()
{
/*
this.client.guilds.cache.tap(guild => {
guild.members.fetch().then(() => {
console.log('Cached ' + guild.members.size + ' members in ' + guild.name);
});
});
*/
this.client.user.setActivity(this.config.discord.activity);
this.client.channels.fetch(this.config.discord.logchannel).then(logChannel => this.logChannel = logChannel);
this.client.channels.fetch(this.config.discord.dmchannel).then(dmChannel => this.dmChannel = dmChannel);
this.client.channels.fetch(this.config.discord.errorchannel).then(errorChannel => this.errorChannel = errorChannel);
if (this.mongoclient) {
this.mongodb = this.mongoclient.db(this.config.database.db);
console.log("Database connected.");
}
console.log("Client ready, loading modules...");
this.loadConfigModules();
console.log("Modules loaded!");
setInterval(() => { this.onTick(); }, 1000);
}
start()
{
Promise.all(this.readyPromises).then(() => {
this.onReady();
}).catch((err) => {
console.error(err);
});
}
stop()
{
var promises = [];
console.log("Stopping client...");
promises.push(this.client.destroy());
if (this.mongoclient) {
console.log("Stopping MongoDB...");
promises.push(this.mongoclient.close());
}
Promise.all(promises).then(() => {
console.log("Client stopped.");
process.exit();
});
}
addLogMessage(text, fromMember)
{
if (!this.logChannel) {
console.log("Couldn't log because we couldn't find the log channel:", text);
return;
}
if (fromMember) {
text += " (via " + fromMember.user.username + ")";
}
console.log("Log: " + text);
this.logChannel.send(":robot: " + text);
}
/**
* Checks if the given member is an admin.
* @param {discord.GuildMember} member
*/
isAdmin(member)
{
return member.permissions.has(discord.Permissions.FLAGS.ADMINISTRATOR);
}
/**
* Checks if the given member is a moderator.
* @param {discord.GuildMember} member
*/
isMod(member)
{
return member.permissions.has(discord.Permissions.FLAGS.MANAGE_MESSAGES);
}
onTick()
{
for (var i = 0; i < this.modules.length; i++) {
var m = this.modules[i];
if (m.onTick) {
m.onTick();
}
}
}
onMemberJoin(member)
{
console.log("User joined: " + member + " (" + member.user.username + ")");
for (var i = 0; i < this.modules.length; i++) {
var m = this.modules[i];
if (m.onMemberJoin) {
m.onMemberJoin(member);
}
}
}
handleError(ex)
{
console.log(ex);
if (this.errorChannel) {
this.errorChannel.send(":octagonal_sign: Bot error!\n```\n" + ex.toString() + "\n```");
}
}
/**
* @param {discord.Message} msg
* @param {Boolean} edited
*/
async onMessage(msg, edited)
{
// Ignore our own messages
if (msg.author == this.client.user) {
return;
}
// Ignore DM's
if (msg.member === null && msg.guild === null) {
// Log DM's
var logUsername = msg.author.username + '#' + msg.author.discriminator;
console.warn("Ignored a DM from " + logUsername.brightWhite + ": \"" + msg.content + "\"");
// Send DM's to the DM channel
if (this.dmChannel) {
this.dmChannel.send(":mailbox_with_mail: " + msg.author.toString() + ": `" + msg.content + "`");
}
return;
}
// Ignore webhooks
if (msg.webhookID) {
console.warn("Ignored webhook: \"" + msg.content + "\"");
return;
}
// Ensure we have a member (sometimes this is null if their status is offline)
if (msg.member === null) {
console.warn("Member is null, fetching member now");
msg.member = await msg.guild.fetchMember(msg.author);
}
// Log line
var logUsername = msg.author.username + '#' + msg.author.discriminator;
if (this.isAdmin(msg.member)) {
logUsername = logUsername.red;
} else if (this.isMod(msg.member)) {
logUsername = logUsername.yellow;
} else {
logUsername = logUsername.brightWhite;
}
console.log('[' + moment().format('MMM Do LTS') + '] '
+ logUsername
+ ' in ' + ('#' + msg.channel.name).green.underline + ': '
+ (edited ? '(edited) '.gray : '')
+ '"' + msg.content + '"');
for (var i = 0; i < this.modules.length; i++) {
var m = this.modules[i];
if (m.onMessage) {
try {
if (m.onMessage(msg, edited)) {
return;
}
} catch (ex) { this.handleError(ex); }
}
}
if (!msg.content.startsWith(".")) {
return;
}
var parse = cmdsplit(msg.content);
var cmdID = parse[0].slice(1);
cmdID = cmdID.charAt(0).toUpperCase() + cmdID.slice(1);
if (!cmdID.match(/^[a-z]+$/i)) {
return;
}
var cmdFound = false;
var cmdFunc = findCommand(this, cmdID);
if (cmdFunc) {
if (msg.member !== null) {
console.log("Built-in command from \"" + msg.member.user.username + "\": " + cmdID);
} else {
console.log("Module command from offline member: " + cmdID);
}
try {
var r = cmdFunc.apply(this, [ msg ].concat(parse.slice(1)));
if (r && r.catch) {
r.catch(ex => this.handleError(ex));
}
} catch (ex) { this.handleError(ex); }
cmdFound = true;
}
for (var i = 0; i < this.modules.length; i++) {
var m = this.modules[i];
var cmdFunc = findCommand(m, cmdID);
if (!cmdFunc) {
continue;
}
if (msg.member !== null) {
console.log("Module command from \"" + msg.member.user.username + "\": " + cmdID);
} else {
console.log("Module command from offline member: " + cmdID);
}
try {
var r = cmdFunc.apply(m, [ msg ].concat(parse.slice(1)));
if (r && r.catch) {
r.catch(ex => this.handleError(ex));
}
} catch (ex) { this.handleError(ex); }
cmdFound = true;
}
if (!cmdFound) {
console.log("Unknown command: \"" + cmdID + "\"");
}
}
async onMessageUpdate(oldMsg, newMsg)
{
if (oldMsg.content != newMsg.content) {
this.onMessage(newMsg, true);
}
}
async onMessageDelete(msg)
{
console.log("Message deleted: \"" + msg.content + "\"");
}
onCmdGithub(msg)
{
msg.channel.send("My code is on Github! :robot: https://github.com/codecat/reddit-radio");
}
/*
//TODO: Move this to a module
onCmdWeather(msg)
{
var url = "https://api.darksky.net/forecast/" + this.config.weather.apikey + "/" + this.config.weather.coords + "?units=auto";
https.get(url, (res) => {
var data = "";
res.setEncoding("utf8");
res.on("data", function(chunk) { data += chunk; });
res.on("end", () => {
try {
var obj = JSON.parse(data);
var ret = "**The weather at Defqon.1 is currently:** (powered by darksky.net)\n";
ret += "*" + obj.currently.summary + "* / **" + obj.currently.temperature + "\u2103 (" + Math.round((obj.currently.temperature * 9/5) + 32) + "\u2109)** / " + Math.round(obj.currently.humidity * 100) + "% humidity\n";
ret += "UV index " + obj.currently.uvIndex + ", wind speed " + obj.currently.windSpeed + " m/s";
msg.channel.send(ret);
} catch (err) {
msg.channel.send("I failed to get the weather... :sob:");
console.log(err);
}
});
});
}
*/
//TODO: Change this to .timeout and move this to its own module
/*
onCmdMute(msg)
{
if (!this.isMod(msg.member)) {
return;
}
for (var memberID of msg.mentions.members.keys()) {
var member = msg.mentions.members.get(memberID);
member.roles.remove(this.config.discord.mutedrole);
this.addLogMessage("Muted " + member.user.username, msg.member);
}
msg.delete();
}
onCmdUnmute(msg)
{
if (!this.isMod(msg.member)) {
return;
}
for (var memberID of msg.mentions.members.keys()) {
var member = msg.mentions.members.get(memberID);
member.roles.add(this.config.discord.mutedrole);
this.addLogMessage("Unmuted " + member.user.username, msg.member);
}
msg.delete();
}
*/
formatMilliseconds(ms)
{
var sec = Math.floor(ms / 1000);
var secs = sec % 60;
var mins = Math.floor(sec / 60) % 60;
var hours = Math.floor(sec / 60 / 60) % 60;
var ret = "";
if (hours > 0) {
ret += hours + "h";
}
if (mins > 0) {
ret += mins + "m";
}
ret += secs + "s";
return ret;
}
onCmdTime(msg)
{
var date = moment();
var text = "The local time is: **" + date.format("HH:mm") + "** (<https://time.is/CET>)";
msg.channel.send(text);
}
}
module.exports = RedditRadio;

101
Startup.js Normal file
View file

@ -0,0 +1,101 @@
var RedditRadio = require("./RedditRadio");
var toml = require("toml");
var fs = require("fs");
var Radio = require("./Radio");
class Startup
{
constructor()
{
this.RADIO = 'radio';
this.RADIOS = 'radios';
this.NO_RADIO = 'no-radio';
this.REGULAR_BOT = 'regular-bot';
this.radios = [];
let configFile = process.env.CONFIG_FILE || "config.toml";
this.config = toml.parse(fs.readFileSync(configFile, "utf8"));
}
run()
{
let args = this.parseArgs();
switch (args.type) {
case this.RADIO: this.setupRadio(args.name); break;
case this.RADIOS: this.setupRadios(); break;
case this.NO_RADIO: this.setupNoRadio(); break;
default: this.startBot(this.config); break;
}
}
setupNoRadio()
{
let config = this.config;
delete config.radios;
this.startBot(config);
}
setupRadio(name)
{
if (this.config.radios === undefined) {
console.error('No radios are defined in the config file, exiting...');
process.exit(1);
}
for (let i = 0; i < this.config.radios.length; i++) {
if (this.config.radios[i].name === name) {
this.startRadio(this.config.radios[i]);
break;
}
}
this.setupSignals();
}
setupRadios()
{
for (var i = 0; i < this.config.radios.length; i++) {
this.startRadio(this.config.radios[i]);
}
this.setupSignals();
}
startRadio(radioConfig)
{
this.radios.push(new Radio(this.config, radioConfig));
}
startBot(config)
{
this.bot = new RedditRadio(config);
this.bot.start();
this.setupSignals();
}
setupSignals()
{
var stopHandler = () => {
if (this.bot) {
this.bot.stop();
}
for (var i = 0; i < this.radios.length; i++) {
this.radios[i].stop();
}
};
process.on("SIGINT", stopHandler); // Ctrl+C
process.on("SIGTERM", stopHandler); // Terminate
}
parseArgs()
{
let args = process.argv.splice(2);
switch (args[0]) {
case '--radio': return { type: this.RADIO, name: args[1]};
case '--radios': return { type: this.RADIOS };
case '--no-radios': return { type: this.NO_RADIO };
default: return { type: this.REGULAR_BOT };
}
}
}
module.exports = Startup;

54
cmdsplit.js Normal file
View file

@ -0,0 +1,54 @@
module.exports = function(str, options) {
var ret = [];
var buffer = "";
var inString = false;
for (var i = 0; i < str.length; i++) {
var c = str[i];
// literals
if (c == "\\" && i + 1 < str.length) {
buffer += str[++i];
continue;
}
// strings
if (c == "\"") {
if (inString) {
// string ends
inString = false;
ret.push(buffer);
buffer = "";
if (i + 1 < str.length && str[i + 1] == " ") {
i++;
}
} else {
// string starts
inString = true;
}
continue;
}
// words
if (c == " ") {
if (inString) {
buffer += " ";
} else {
ret.push(buffer);
buffer = "";
}
continue;
}
// characters
buffer += c;
}
// last word
if (buffer != "") {
ret.push(buffer);
}
return ret;
};

30
config.example.toml Normal file
View file

@ -0,0 +1,30 @@
[discord]
token = "main_bot_token"
timezone = "Europe/Amsterdam"
logchannel = "647767937054277642"
dmchannel = "736529983706628146"
#[database]
#url = "mongodb://localhost:27017"
#db = "redditradio"
[filter]
badwords = "(thepiratebay|badwords)"
[[modules.qdance]]
#[[modules.event]]
#file = "events/Qlimax2019.json"
#[[modules.eventquick]]
#channel = "651911168650248208"
[[radios]]
token = "radio_bot_1_token"
channel = "330121740758024193"
name = "Q-dance Radio"
url = "http://audio.true.nl/qdance-hard"
[voice]
passes = 2
bitrate = 44100

View file

@ -0,0 +1,62 @@
[
{
"stage": "I AM HARDSTYLE - Born & Raised",
"channel": "319525278978277407",
"emoji": ":warning:",
"url": "https://www.youtube.com/watch?v=Y9KxiNOKaCI",
"unconfirmed": false,
"streamdelay": 0,
"sets": [
[ 2020, 3, 14, 20, 0, "Toneshifterz" ],
[ 2020, 3, 14, 21, 0, "Brennan Heart" ],
[ 2020, 3, 14, 22, 0, "Code Black" ],
[ 2020, 3, 14, 23, 0, "Brennan Heart, Code Black, Dailucia & Toneshifterz" ],
[ 2020, 3, 15, 0, 0 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://www.youtube.com/watch?v=Y9KxiNOKaCI>**",
"^\\.(corona)$": "Due to many hardstyle events being canceled due to the COVID-19 pandemic, several DJ's are setting up their own livestream events. For all Corona/Event related news: <https://redd.it/fh1kvr>"
}
},
{
"stage": "Quarantine Hardstyle Sessions",
"channel": "688383827407667272",
"emoji": ":wolf:",
"url": "https://www.facebook.com/Qdance/videos/518799009013830/",
"unconfirmed": false,
"streamdelay": 0,
"sets": [
[ 2020, 3, 14, 22, 0, "Early Hardstyle by Jones" ],
[ 2020, 3, 14, 22, 30, "Frequencerz" ],
[ 2020, 3, 14, 23, 0, "Ran-D & Adaro" ],
[ 2020, 3, 14, 23, 30, "Devin Wild" ],
[ 2020, 3, 15, 0, 0, "B-Front" ],
[ 2020, 3, 15, 0, 30, "Ending" ],
[ 2020, 3, 15, 1, 35 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://www.facebook.com/Qdance/videos/518799009013830/>**",
"^\\.(mc|host)$": ":microphone: Hosted by **MC Da Syndrone**.",
"^\\.(corona)$": "Due to many hardstyle events being canceled due to the COVID-19 pandemic, several DJ's are setting up their own livestream events. For all Corona/Event related news: <https://redd.it/fh1kvr>"
}
},
{
"stage": "United We Stand",
"channel": "688470315579605189",
"emoji": "<:frontliner:376092032361299979>",
"url": "https://www.youtube.com/watch?v=6B8OU5aQR_4",
"unconfirmed": true,
"streamdelay": 0,
"sets": [
[ 2020, 3, 14, 21, 0, "Blasterjaxx" ],
[ 2020, 3, 14, 22, 0, "Frontliner" ],
[ 2020, 3, 14, 22, 45, "Blasterjaxx & Frontliner" ],
[ 2020, 3, 14, 23, 15, "Mr. Polska" ],
[ 2020, 3, 14, 23, 30 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://www.youtube.com/watch?v=6B8OU5aQR_4>**",
"^\\.(corona)$": "Due to many hardstyle events being canceled due to the COVID-19 pandemic, several DJ's are setting up their own livestream events. For all Corona/Event related news: <https://redd.it/fh1kvr>"
}
}
]

View file

@ -0,0 +1,27 @@
[
{
"stage": "Quarantine Hardstyle Sessions",
"channel": "319525278978277407",
"emoji": ":wolf:",
"url": "https://www.youtube.com/watch?v=tA6ZsLKm_SU",
"unconfirmed": false,
"streamdelay": 0,
"sets": [
[ 2020, 3, 20, 19, 0, "Unrivalled Podcast ft. Delius" ],
[ 2020, 3, 20, 20, 0, "Frequencerz (Hardstyle Classics)" ],
[ 2020, 3, 20, 20, 30, "B-Freqz" ],
[ 2020, 3, 20, 21, 0, "Rejecta" ],
[ 2020, 3, 20, 21, 30, "Radianze" ],
[ 2020, 3, 20, 22, 0, "Digital Punk" ],
[ 2020, 3, 20, 22, 30, "D-Fence" ],
[ 2020, 3, 20, 23, 0, "Ending" ],
[ 2020, 3, 21, 0, 0, "Feels birthday man" ],
[ 2020, 3, 21, 0, 17 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://www.youtube.com/watch?v=tA6ZsLKm_SU>**",
"^\\.(mc|host)$": ":microphone: Hosted by **MC Da Syndrome**.",
"^\\.(corona)$": "Due to many hardstyle events being canceled due to the COVID-19 pandemic, several DJ's are setting up their own livestream events. For all Corona/Event related news: <https://redd.it/fh1kvr>"
}
}
]

View file

@ -0,0 +1,24 @@
[
{
"stage": "I AM HARDSTYLE",
"channel": "319525278978277407",
"emoji": ":warning:",
"url": "https://www.youtube.com/watch?v=0jWt1ZQd7Gg",
"unconfirmed": false,
"streamdelay": 0,
"sets": [
[ 2020, 4, 12, 19, 0, "Code Black" ],
[ 2020, 4, 12, 19, 45, "Dailucia" ],
[ 2020, 4, 12, 20, 15, "Aftershock" ],
[ 2020, 4, 12, 20, 45, "Toneshifterz" ],
[ 2020, 4, 12, 21, 15, "Brennan Heart" ],
[ 2020, 4, 12, 22, 15, "Radical Redemption & MC Nolz" ],
[ 2020, 4, 12, 23, 0 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://www.youtube.com/watch?v=0jWt1ZQd7Gg>**",
"^\\.(corona)$": "Due to many hardstyle events being canceled due to the COVID-19 pandemic, several DJ's are setting up their own livestream events. For all Corona/Event related news: <https://redd.it/fh1kvr>",
"^\\.(ultrawide)$": "For the ultrawide viewers, this userscript is nice: <https://greasyfork.org/en/scripts/396195-video-zooming-buttons>"
}
}
]

37
events/Dediqated2020.json Normal file
View file

@ -0,0 +1,37 @@
[
{
"stage": "mainstage",
"channel": "319525278978277407",
"emoji": "<:qdance:328585093553586176>",
"url": "https://www.q-dance.com/en/videos/dediqated-live",
"unconfirmed": true,
"streamdelay": 3,
"sets": [
[ 2020, 2, 8, 12, 50, "Early Rave Rebels", "Buzz Fuzz, Franky Jones, Gizmo, The Darkraver" ],
[ 2020, 2, 8, 13, 13, "The Qlubtempo Parade", "ASYS, DJ Luna, Dana, Pavo, Pila" ],
[ 2020, 2, 8, 13, 41, "Fusion Records", "Donkey Rollers, The Pitcher, Zany" ],
[ 2020, 2, 8, 13, 58, "Bella Hardstyle Italia", "Tatanka, TNT, Zatox" ],
[ 2020, 2, 8, 14, 30, "Masters of Melody Part 1", "Bass Modulators, Frontliner, Max Enforcer" ],
[ 2020, 2, 8, 15, 0, "Praise the Reverse Bass", "DJ Isaac, Technoboy, Tuneboy" ],
[ 2020, 2, 8, 15, 20, "Viva Hollandia", "Deepack, Dr. Rude, Outsiders, Ruthless, The Viper" ],
[ 2020, 2, 8, 15, 58, "World of Madness", "Headhunterz, Noisecontrollers, Wildstylez" ],
[ 2020, 2, 8, 16, 44, "Millenium Hardcore Mayhem", "Art of Fighters, Endymion, Evil Activities, Noize Supperssor, Nosferatu, Promo" ],
[ 2020, 2, 8, 17, 16, "Scantraxx Recordz", "D-Block & S-te-Fan, Devin Wild, The Prophet" ],
[ 2020, 2, 8, 17, 37, "The Land Down Under", "Audiofreq, Code Black, Outbreak" ],
[ 2020, 2, 8, 17, 52, "The Best of Power Hour", "Neophyte, The Stunned Guys, Zazafront and many more" ],
[ 2020, 2, 8, 18, 53, "Dirty Workz", "Coone, Da Tweekaz, Hard Driver, Psyko Punkz" ],
[ 2020, 2, 8, 19, 20, "Birth of Raw", "Adaro, Alpha Twins, B-Front, Crypsis, Digital Punk, E-Force, Ran-D" ],
[ 2020, 2, 8, 20, 4, "Masters of Melody Part 2", "Atmozfears, Audiotricz, KELTEK, Sound Rush" ],
[ 2020, 2, 8, 20, 36, "Future Heroes", "D-Sturb, Frequencerz, Rebelion, Sefa, Sub Zero Project, Warface" ],
[ 2020, 2, 8, 21, 22, "Hardcore 2.0", "Dr. Peacock, Korsakoff, Mad Dog, Partyraiser" ],
[ 2020, 2, 8, 22, 0, "Hardstyle Top 25 of All-Time" ],
[ 2020, 2, 8, 22, 50, "The Endshow" ],
[ 2020, 2, 8, 23, 0 ]
],
"responses": {
"^\\.(top100|top25)": "Find the all-time top 100 here: <#675627527208697856>",
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://www.q-dance.com/en/videos/dediqated-live>**",
"^\\.(mc|host)$": ":microphone: The MCs are **Villain**, **DV8**, and **Ruffian**."
}
}
]

117
events/Defqon2018.json Normal file
View file

@ -0,0 +1,117 @@
[
{
"stage": "red",
"mc": "DV8 & Villain",
"channel": "459077085013606401",
"emoji": ":red_circle:",
"url": "https://www.q-dance.com/en/videos/defqon-1-livestream-red",
"sets": [
[ 23, 13, 0, "ANDY SVGE" ],
[ 23, 14, 0, "Sound Rush" ],
[ 23, 15, 0, "Noisecontrollers" ],
[ 23, 16, 0, "POWER HOUR" ],
[ 23, 17, 0, "Code Black" ],
[ 23, 18, 0, "D-Block & S-te-Fan" ],
[ 23, 19, 0, "Wildstylez" ],
[ 23, 20, 0, "Ran-D (LIVE)" ],
[ 23, 20, 45, "Warface" ],
[ 23, 21, 45, "Sub Zero Project" ],
[ 23, 22, 30, "The Endshow" ],
[ 23, 23, 0, "Nothing" ],
[ 24, 15, 0, "Zazafront" ],
[ 24, 15, 30, "Wasted Penguinz & Adrenalize" ],
[ 24, 16, 15, "Ransomnia & Friends" ],
[ 24, 16, 45, "Psyko Punkz" ],
[ 24, 17, 30, "Dee-Block & S-te-Pack" ],
[ 24, 18, 15, "Zatox (LIVE)" ],
[ 24, 18, 45, "Peacock in Concert" ],
[ 24, 19, 15, "Tweekacore & Darren Styles" ],
[ 24, 20, 0, "Brennan Heart (Uncertain)" ],
[ 24, 21, 0, "Coone" ],
[ 24, 22, 0, "Project One" ],
[ 24, 23, 0, "Nothing" ]
]
},
{
"stage": "blue",
"mc": "Nolz",
"channel": "459077096665382942",
"emoji": ":large_blue_circle:",
"url": "https://www.q-dance.com/en/videos/defqon-1-livestream-blue",
"sets": [
[ 22, 20, 0, "Headhunterz" ],
[ 22, 21, 0, "Bass Modulators" ],
[ 22, 22, 0, "Atmozfears" ],
[ 22, 23, 0, "B-Front & Phuture Noize" ],
[ 23, 0, 0, "Frequencerz" ],
[ 23, 1, 0, "Nothing" ],
[ 23, 13, 0, "Requiem" ],
[ 23, 14, 0, "Digital Punk" ],
[ 23, 15, 0, "E-Force" ],
[ 23, 16, 0, "Regain (LIVE) (Uncertain)" ],
[ 23, 16, 30, "Jason Payne" ],
[ 23, 18, 0, "B-Freqz" ],
[ 23, 18, 30, "Adaro" ],
[ 23, 19, 30, "Radical Redemption (LIVE)" ],
[ 23, 20, 0, "Rebelion" ],
[ 23, 21, 0, "Main Concern" ],
[ 23, 22, 0, "Nothing" ],
[ 24, 13, 0, "Myst" ],
[ 24, 14, 30, "Act of Rage" ],
[ 24, 15, 30, "Clockartz" ],
[ 24, 16, 30, "Endymion (LIVE)" ],
[ 24, 17, 0, "Crypsis & Chain Reaction present \"Unlike Others\"" ],
[ 24, 17, 45, "Sub Sonik" ],
[ 24, 18, 45, "D-Sturb (LIVE)" ],
[ 24, 19, 15, "Frequencerz \"Stealth Mode\"" ],
[ 24, 19, 45, "Phuture Noize presents Black Mirror Society (Showcase)" ],
[ 24, 20, 30, "Deetox - \"Bring the Riot\" (LIVE)" ],
[ 24, 21, 0, "Delete VIP (Uncertain)" ],
[ 24, 21, 30, "Rebel[ut]ion" ],
[ 24, 22, 0, "Nothing" ]
]
},
{
"stage": "black",
"mc": "Diesel",
"channel": "459077105137745941",
"emoji": ":black_circle:",
"url": "https://www.q-dance.com/en/videos/defqon-1-livestream-black",
"sets": [
[ 22, 20, 0, "Paul Elstak" ],
[ 22, 21, 0, "20 Years of Endymion (LIVE)" ],
[ 22, 22, 0, "Korsakoff" ],
[ 22, 23, 0, "Tha Playah" ],
[ 23, 0, 0, "Destructive Tendencies" ],
[ 23, 1, 0, "Nothing" ],
[ 23, 13, 0, "Neophyte" ],
[ 23, 14, 0, "Mad Dog" ],
[ 23, 15, 0, "Noize Suppressor" ],
[ 23, 16, 0, "Art of Fighters" ],
[ 23, 17, 0, "Miss K8" ],
[ 23, 18, 0, "N-Vitral presents \"Bombsquad\" (Uncertain)" ],
[ 23, 18, 30, "Angerfist" ],
[ 23, 19, 30, "Andy The Core" ],
[ 23, 20, 30, "The Sickest Squad" ],
[ 23, 21, 30, "Spitnoise" ],
[ 23, 22, 30, "Nothing" ],
[ 24, 14, 0, "The Melodyst" ],
[ 24, 15, 0, "Anime" ],
[ 24, 16, 0, "Nosferatu" ],
[ 24, 17, 0, "Evil Activities" ],
[ 24, 18, 0, "I:GOR (Uncertain)" ],
[ 24, 19, 0, "Deadly Guns" ],
[ 24, 20, 0, "D-Fence" ],
[ 24, 21, 0, "Partyraiser & Bulletproof" ],
[ 24, 22, 0, "Sefa" ],
[ 24, 23, 0, "Nothing" ]
]
}
]

View file

@ -0,0 +1,27 @@
[
{
"stage": "red",
"mc": "Villain",
"channel": "489754437624266766",
"emoji": ":red_circle:",
"url": "https://www.q-dance.com/en/",
"sets": [
[ 15, 8, 15, "TNT" ],
[ 15, 9, 15, "Defqon.1 Legends presents: A Decade of Dedication" ],
[ 15, 10, 15, "Sub Zero Project" ],
[ 15, 10, 40, "Da Tweekaz" ],
[ 15, 11, 38, "Coone" ],
[ 15, 12, 38, "Headhunterz" ],
[ 15, 13, 50, "The Endshow" ],
[ 15, 14, 6, "Nothing" ],
[ 15, 16, 0, "TNT (Rebroadcast)" ],
[ 15, 17, 0, "Defqon.1 Legends presents: A Decade of Dedication (Rebroadcast)" ],
[ 15, 18, 0, "Sub Zero Project (Rebroadcast)" ],
[ 15, 18, 30, "Da Tweekaz (Rebroadcast)" ],
[ 15, 19, 30, "Coone (Rebroadcast)" ],
[ 15, 20, 30, "Headhunterz (Rebroadcast)" ],
[ 15, 21, 45, "The Endshow (Rebroadcast)" ],
[ 15, 22, 0, "Nothing" ]
]
}
]

117
events/Defqon2018_test.json Normal file
View file

@ -0,0 +1,117 @@
[
{
"stage": "red",
"mc": "DV8 & Villain",
"channel": "459077085013606401",
"emoji": ":red_circle:",
"url": "https://www.q-dance.com/en/news/defqon-1-2018-or-livestream",
"sets": [
[ 23, 13, 0, "ANDY SVGE" ],
[ 23, 14, 0, "Sound Rush" ],
[ 23, 15, 0, "Noisecontrollers" ],
[ 23, 16, 0, "POWER HOUR" ],
[ 23, 17, 0, "Code Black" ],
[ 23, 18, 0, "D-Block & S-te-Fan" ],
[ 23, 19, 0, "Wildstylez" ],
[ 23, 20, 0, "Ran-D (LIVE)" ],
[ 23, 20, 45, "Warface" ],
[ 23, 21, 45, "Sub Zero Project" ],
[ 23, 22, 30, "The Endshow" ],
[ 23, 23, 0, "Nothing" ],
[ 24, 15, 0, "Zazafront" ],
[ 24, 15, 30, "Wasted Penguinz & Adrenalize" ],
[ 24, 16, 15, "Ransomnia & Friends" ],
[ 24, 16, 45, "Psyko Punkz" ],
[ 24, 17, 30, "Dee-Block & S-te-Pack" ],
[ 24, 18, 15, "Zatox (LIVE)" ],
[ 24, 18, 45, "Tweekacore & Darren Styles" ],
[ 24, 19, 30, "Peacock in Concert" ],
[ 24, 20, 0, "Nothing" ],
[ 24, 21, 0, "Coone" ],
[ 24, 22, 0, "Project One" ],
[ 24, 23, 0, "Nothing" ]
]
},
{
"stage": "blue",
"mc": "Villain",
"channel": "459077096665382942",
"emoji": ":large_blue_circle:",
"url": "https://www.q-dance.com/en/news/defqon-1-2018-or-livestream",
"sets": [
[ 20, 21, 38, "Headhunterz" ],
[ 22, 21, 0, "Bass Modulators" ],
[ 22, 22, 0, "Atmozfears" ],
[ 22, 23, 0, "B-Front & Phuture Noize" ],
[ 23, 0, 0, "Frequencerz" ],
[ 23, 1, 0, "Nothing" ],
[ 23, 13, 0, "Requiem" ],
[ 23, 14, 0, "Digital Punk" ],
[ 23, 15, 0, "E-Force" ],
[ 23, 16, 0, "Nothing" ],
[ 23, 16, 30, "Jason Payne" ],
[ 23, 18, 0, "B-Freqz" ],
[ 23, 18, 30, "Adaro" ],
[ 23, 19, 30, "Radical Redemption (LIVE)" ],
[ 23, 20, 0, "Rebelion" ],
[ 23, 21, 0, "Main Concern" ],
[ 23, 22, 0, "Nothing" ],
[ 24, 13, 0, "Myst" ],
[ 24, 14, 30, "Act of Rage" ],
[ 24, 15, 30, "Clockartz" ],
[ 24, 16, 30, "Endymion (LIVE)" ],
[ 24, 17, 0, "Crypsis & Chain Reaction present \"Unlike Others\"" ],
[ 24, 17, 45, "Sub Sonik" ],
[ 24, 18, 45, "D-Sturb (LIVE)" ],
[ 24, 19, 15, "Frequencerz \"Stealth Mode\"" ],
[ 24, 19, 45, "Phuture Noize presents Black Mirror Society (Showcase)" ],
[ 24, 20, 30, "Deetox - \"Bring the Riot\" (LIVE)" ],
[ 24, 21, 0, "Nothing" ],
[ 24, 21, 30, "Rebel[ut]ion" ],
[ 24, 22, 0, "Nothing" ]
]
},
{
"stage": "black",
"mc": "Ruffian",
"channel": "459077105137745941",
"emoji": ":black_circle:",
"url": "https://www.q-dance.com/en/news/defqon-1-2018-or-livestream",
"sets": [
[ 22, 20, 0, "Paul Elstak" ],
[ 22, 21, 0, "20 Years of Endymion (LIVE)" ],
[ 22, 22, 0, "Korsakoff" ],
[ 22, 23, 0, "Tha Playah" ],
[ 23, 0, 0, "Destructive Tendencies" ],
[ 23, 1, 0, "Nothing" ],
[ 23, 13, 0, "Neophyte" ],
[ 23, 14, 0, "Mad Dog" ],
[ 23, 15, 0, "Nothing" ],
[ 23, 16, 0, "Art of Fighters" ],
[ 23, 17, 0, "Miss K8" ],
[ 23, 18, 0, "Nothing" ],
[ 23, 18, 30, "Angerfist" ],
[ 23, 19, 30, "Andy The Core" ],
[ 23, 20, 30, "The Sickest Squad" ],
[ 23, 21, 30, "Spitnoise" ],
[ 23, 22, 30, "Nothing" ],
[ 24, 14, 0, "The Melodyst" ],
[ 24, 15, 0, "Anime" ],
[ 24, 16, 0, "Nosferatu" ],
[ 24, 17, 0, "Evil Activities" ],
[ 24, 18, 0, "Nothing" ],
[ 24, 19, 0, "Deadly Guns" ],
[ 24, 20, 0, "D-Fence" ],
[ 24, 21, 0, "Partyraiser & Bulletproof" ],
[ 24, 22, 0, "Sefa" ],
[ 24, 23, 0, "Nothing" ]
]
}
]

352
events/Defqon2019.json Normal file
View file

@ -0,0 +1,352 @@
[
{
"stage": "Red",
"mc": "Villain & DV8",
"channel": "591927725594378240",
"emoji": ":heart:",
"url": "https://www.q-dance.com/en/videos/red-live-2019",
"sets": [
[ 2019, 6, 29, 11, 3, "Toneshifterz" ],
[ 2019, 6, 29, 12, 3, "Adrenalize & Devin Wild" ],
[ 2019, 6, 29, 13, 3, "Coone" ],
[ 2019, 6, 29, 13, 48, "Noisecontrollers" ],
[ 2019, 6, 29, 14, 33, "Sound Rush" ],
[ 2019, 6, 29, 15, 18, "Headhunterz" ],
[ 2019, 6, 29, 16, 3, "Power Hour" ],
[ 2019, 6, 29, 17, 3, "Coone, Headhunterz,Noisecontrollers & Sound Rush" ],
[ 2019, 6, 29, 18, 3, "KELTEK" ],
[ 2019, 6, 29, 19, 3, "Frequencerz & Phuture Noize" ],
[ 2019, 6, 29, 20, 3, "D-Sturb" ],
[ 2019, 6, 29, 21, 3, "Radical Redemption" ],
[ 2019, 6, 29, 21, 48, "B-Front" ],
[ 2019, 6, 29, 22, 38, "The Endshow" ],
[ 2019, 6, 29, 23, 0, "Nothing" ],
[ 2019, 6, 30, 15, 3, "Code Black & Atmozfears" ],
[ 2019, 6, 30, 16, 18, "Audiotricz: The Next Chapter [LIVE]" ],
[ 2019, 6, 30, 17, 3, "D-Block & S-te-Fan & DJ Isaac" ],
[ 2019, 6, 30, 18, 3, "Wildstylez" ],
[ 2019, 6, 30, 19, 3, "Gunz for Hire" ],
[ 2019, 6, 30, 20, 3, "Defqon.1 Legends" ],
[ 2019, 6, 30, 21, 18, "Sub Zero Project" ],
[ 2019, 6, 30, 22, 13, "Sefa" ],
[ 2019, 6, 30, 22, 53, "The Closing Ritual" ],
[ 2019, 6, 30, 23, 0, "Nothing" ]
],
"faq": [
]
},
{
"stage": "Blue",
"mc": "Villain & Livid & Nolz",
"channel": "591928007351074816",
"emoji": ":blue_heart:",
"url": "https://www.q-dance.com/en/videos/blue-live-2019",
"sets": [
[ 2019, 6, 28, 20, 3, "Brennan Heart" ],
[ 2019, 6, 28, 21, 33, "Bass Modulators" ],
[ 2019, 6, 28, 22, 33, "D-Block & S-te-Fan" ],
[ 2019, 6, 28, 23, 33, "Ran-D" ],
[ 2019, 6, 29, 0, 33, "Phuture Noize [THE SPOTLIGHT]" ],
[ 2019, 6, 29, 1, 0, "Nothing" ],
[ 2019, 6, 29, 11, 3, "D-Attack" ],
[ 2019, 6, 29, 12, 3, "Bass Chaserz" ],
[ 2019, 6, 29, 13, 3, "Sub Sonik" ],
[ 2019, 6, 29, 14, 3, "Deetox presents Revival" ],
[ 2019, 6, 29, 15, 3, "Hard Driver [LIVE]" ],
[ 2019, 6, 29, 15, 33, "Malice" ],
[ 2019, 6, 29, 17, 3, "Digital Punk" ],
[ 2019, 6, 29, 18, 3, "Rejecta" ],
[ 2019, 6, 29, 19, 3, "Warface" ],
[ 2019, 6, 29, 20, 3, "Regain" ],
[ 2019, 6, 29, 21, 3, "Rebelion" ],
[ 2019, 6, 29, 22, 0, "Nothing" ],
[ 2019, 6, 30, 0, 0, "Psyko Punkz" ],
[ 2019, 6, 30, 1, 0, "Zatox" ],
[ 2019, 6, 30, 2, 0, "Rebelion & Delete" ],
[ 2019, 6, 30, 3, 0, "The Sickest Squad" ],
[ 2019, 6, 30, 4, 0, "Nothing" ],
[ 2019, 6, 30, 11, 3, "Chain Reaction" ],
[ 2019, 6, 30, 12, 3, "Degos & Re-Done" ],
[ 2019, 6, 30, 13, 3, "Endymion" ],
[ 2019, 6, 30, 14, 3, "Clockartz" ],
[ 2019, 6, 30, 14, 48, "Requiem presents The Reckoning" ],
[ 2019, 6, 30, 15, 18, "Titan" ],
[ 2019, 6, 30, 15, 33, "Act of Rage" ],
[ 2019, 6, 30, 16, 33, "Adaro" ],
[ 2019, 6, 30, 17, 33, "MYST" ],
[ 2019, 6, 30, 18, 33, "Jason Payne" ],
[ 2019, 6, 30, 19, 33, "Killshot" ],
[ 2019, 6, 30, 20, 33, "E-Force & Luna" ],
[ 2019, 6, 30, 21, 33, "10 Years of Thera" ],
[ 2019, 6, 30, 22, 33, "Delete VIP" ],
[ 2019, 6, 30, 23, 0, "Nothing" ]
],
"faq": [
]
},
{
"stage": "Black",
"mc": "Da Mouth of Madness & Alee",
"channel": "591928033523269632",
"emoji": ":black_heart:",
"url": "https://www.q-dance.com/en/videos/black-live-2019",
"sets": [
[ 2019, 6, 28, 20, 3, "Promo" ],
[ 2019, 6, 28, 21, 33, "Evil Activities" ],
[ 2019, 6, 28, 22, 33, "AniMe" ],
[ 2019, 6, 28, 23, 33, "Angerfist" ],
[ 2019, 6, 29, 0, 33, "Dr. Peacock [THE SPOTLIGHT]" ],
[ 2019, 6, 29, 1, 0, "Nothing" ],
[ 2019, 6, 29, 11, 3, "Access One" ],
[ 2019, 6, 29, 12, 33, "Amada & Yoshiko" ],
[ 2019, 6, 29, 14, 3, "Wasted Mind" ],
[ 2019, 6, 29, 15, 3, "Dither presents Tools of Demolition" ],
[ 2019, 6, 29, 16, 3, "Neophyte" ],
[ 2019, 6, 29, 17, 3, "N-Vitral" ],
[ 2019, 6, 29, 18, 3, "Nosferatu" ],
[ 2019, 6, 29, 19, 3, "Angerfist & I:GOR" ],
[ 2019, 6, 29, 20, 3, "Deadly Guns" ],
[ 2019, 6, 29, 21, 3, "Andy The Core" ],
[ 2019, 6, 29, 22, 3, "Destructive Tendencies" ],
[ 2019, 6, 29, 23, 0, "Nothing" ],
[ 2019, 6, 30, 12, 3, "The Viper" ],
[ 2019, 6, 30, 13, 3, "The Melodyst" ],
[ 2019, 6, 30, 14, 3, "Mad Dog" ],
[ 2019, 6, 30, 15, 3, "Korsakoff" ],
[ 2019, 6, 30, 16, 3, "Tha Playah 'Sick and Twisted showcase'" ],
[ 2019, 6, 30, 17, 3, "D-Fence" ],
[ 2019, 6, 30, 18, 3, "Partyraiser, Hard Effectz & Bulletproof" ],
[ 2019, 6, 30, 19, 3, "GPF [THE PRERECORDED SEX SHOW V69 UNMASTERED TEST.WMA]" ],
[ 2019, 6, 30, 19, 45, "Nothing" ]
],
"faq": [
]
},
{
"stage": "UV",
"mc": "DL & DV8 & Da Syndrome",
"channel": "591928065416757248",
"emoji": ":purple_heart:",
"url": "https://www.q-dance.com/en/videos/uv-live-2019",
"sets": [
[ 2019, 6, 29, 11, 0, "Sephyx" ],
[ 2019, 6, 29, 12, 0, "Retrospect" ],
[ 2019, 6, 29, 13, 0, "Primeshock" ],
[ 2019, 6, 29, 14, 0, "Audiofreq" ],
[ 2019, 6, 29, 15, 0, "Max Enforcer & ANDY SVGE" ],
[ 2019, 6, 29, 16, 0, "Jay Reeve presents A Higher State" ],
[ 2019, 6, 29, 17, 0, "DJ The Prophet" ],
[ 2019, 6, 29, 17, 45, "Zany presents DNA" ],
[ 2019, 6, 29, 18, 45, "Demi Kanon" ],
[ 2019, 6, 29, 19, 30, "Frontliner" ],
[ 2019, 6, 29, 20, 30, "Da Tweekaz" ],
[ 2019, 6, 29, 21, 30, "Wasted Penguinz" ],
[ 2019, 6, 29, 22, 30, "Nothing" ],
[ 2019, 6, 30, 13, 3, "Mental Twister" ],
[ 2019, 6, 30, 14, 3, "Dr. Phunk & MANDY" ],
[ 2019, 6, 30, 15, 3, "Ransom" ],
[ 2019, 6, 30, 15, 48, "Mark with a K & MC Chucky" ],
[ 2019, 6, 30, 16, 33, "Dr. Ruthless" ],
[ 2019, 6, 30, 17, 18, "Outsiders & The Partysquad & The Darkraver" ],
[ 2019, 6, 30, 18, 18, "Frequencerz presents GET WACK!" ],
[ 2019, 6, 30, 19, 18, "ZazaFront" ],
[ 2019, 6, 30, 20, 3, "Mashup Jack" ],
[ 2019, 6, 30, 20, 48, "Paul Elstak" ],
[ 2019, 6, 30, 21, 30, "Nothing" ]
],
"faq": [
]
},
{
"stage": "Magenta",
"mc": "Ruffian & DL",
"channel": "591928254877794324",
"emoji": ":loud_sound:",
"url": "https://www.q-dance.com/en/videos/magenta-audio-live-2019",
"sets": [
[ 2019, 6, 29, 11, 0, "Sunny D" ],
[ 2019, 6, 29, 12, 30, "Pila & The Scientist" ],
[ 2019, 6, 29, 14, 0, "Trilok & Chiren" ],
[ 2019, 6, 29, 15, 30, "ASYS" ],
[ 2019, 6, 29, 17, 0, "Dana" ],
[ 2019, 6, 29, 18, 0, "Activator" ],
[ 2019, 6, 29, 19, 0, "DJ Isaac" ],
[ 2019, 6, 29, 20, 0, "Luna" ],
[ 2019, 6, 29, 21, 30, "Pavo" ],
[ 2019, 6, 29, 23, 0, "Nothing" ],
[ 2019, 6, 30, 11, 0, "Lip DJ" ],
[ 2019, 6, 30, 12, 30, "A-Lusion" ],
[ 2019, 6, 30, 14, 0, "Ivan Carsten" ],
[ 2019, 6, 30, 15, 0, "Geck-o" ],
[ 2019, 6, 30, 16, 0, "The Pitcher" ],
[ 2019, 6, 30, 17, 0, "Tat & Zat" ],
[ 2019, 6, 30, 18, 0, "Charly Lownoise" ],
[ 2019, 6, 30, 19, 0, "Deepack" ],
[ 2019, 6, 30, 20, 0, "Nothing" ]
],
"faq": [
]
},
{
"stage": "Indigo",
"mc": "Focus & Livid",
"channel": "591928282295828480",
"emoji": ":loud_sound:",
"url": "https://www.q-dance.com/en/videos/indigo-audio-live-2019",
"sets": [
[ 2019, 6, 29, 11, 0, "Imperatorz" ],
[ 2019, 6, 29, 12, 0, "Aversion" ],
[ 2019, 6, 29, 13, 0, "Crystal Mad" ],
[ 2019, 6, 29, 14, 0, "Neroz" ],
[ 2019, 6, 29, 15, 0, "Luminite" ],
[ 2019, 6, 29, 16, 0, "Retaliation" ],
[ 2019, 6, 29, 17, 0, "Ncrypta" ],
[ 2019, 6, 29, 18, 0, "The Apexx Machine" ],
[ 2019, 6, 29, 19, 0, "Rooler" ],
[ 2019, 6, 29, 20, 0, "The Purge" ],
[ 2019, 6, 29, 21, 0, "Mind Dimension" ],
[ 2019, 6, 29, 22, 0, "Riot Shift" ],
[ 2019, 6, 29, 23, 0, "Nothing" ],
[ 2019, 6, 30, 11, 0, "Dark Pact" ],
[ 2019, 6, 30, 12, 0, "Tartaros" ],
[ 2019, 6, 30, 13, 0, "The Classics Machine" ],
[ 2019, 6, 30, 14, 0, "RVAGE" ],
[ 2019, 6, 30, 15, 0, "Vertile" ],
[ 2019, 6, 30, 16, 0, "Prefix & Density" ],
[ 2019, 6, 30, 17, 0, "Typhoon" ],
[ 2019, 6, 30, 18, 0, "Bloodlust" ],
[ 2019, 6, 30, 19, 0, "Thyron" ],
[ 2019, 6, 30, 20, 0, "Chris One" ],
[ 2019, 6, 30, 21, 0, "Unresolved" ],
[ 2019, 6, 30, 22, 0, "Caine" ],
[ 2019, 6, 30, 23, 0, "Nothing" ]
],
"faq": [
]
},
{
"stage": "Yellow",
"mc": "No-iD & RG",
"channel": "591928310582345738",
"emoji": ":loud_sound:",
"url": "https://www.q-dance.com/en/videos/yellow-audio-live-2019",
"sets": [
[ 2019, 6, 29, 11, 0, "Streiks & Kratchs" ],
[ 2019, 6, 29, 12, 0, "Mr. Ivex" ],
[ 2019, 6, 29, 13, 0, "Trespassed & Hard Effectz" ],
[ 2019, 6, 29, 14, 0, "Lady Dammage" ],
[ 2019, 6, 29, 15, 0, "Unproven" ],
[ 2019, 6, 29, 16, 0, "BillX" ],
[ 2019, 6, 29, 17, 0, "Crypton" ],
[ 2019, 6, 29, 18, 0, "Spitnoise" ],
[ 2019, 6, 29, 19, 0, "Partyraiser & Bulletproof" ],
[ 2019, 6, 29, 20, 0, "F. NøIzE" ],
[ 2019, 6, 29, 21, 0, "The Destroyer" ],
[ 2019, 6, 29, 22, 0, "Dissoactive" ],
[ 2019, 6, 29, 23, 0, "Nothing" ],
[ 2019, 6, 30, 12, 0, "Sprinky" ],
[ 2019, 6, 30, 13, 30, "Hyrule War" ],
[ 2019, 6, 30, 14, 30, "Remzcore" ],
[ 2019, 6, 30, 15, 30, "Super Trash Bros. [LIVE]" ],
[ 2019, 6, 30, 16, 0, "Frenchcore Familia" ],
[ 2019, 6, 30, 17, 0, "Lunatic" ],
[ 2019, 6, 30, 18, 0, "Vandal!sm" ],
[ 2019, 6, 30, 19, 0, "System Overload" ],
[ 2019, 6, 30, 20, 0, "Angernoizer & Cryogenic" ],
[ 2019, 6, 30, 21, 0, "Chaotic Hostility" ],
[ 2019, 6, 30, 22, 0, "DRS" ],
[ 2019, 6, 30, 23, 0, "Nothing" ]
],
"faq": [
]
},
{
"stage": "Gold",
"mc": "Da Mouth of Madness & Ruffian",
"channel": "591928331159601154",
"emoji": ":loud_sound:",
"url": "https://www.q-dance.com/en/videos/gold-audio-live-2019",
"sets": [
[ 2019, 6, 29, 11, 0, "The Raver" ],
[ 2019, 6, 29, 12, 30, "Petrov" ],
[ 2019, 6, 29, 14, 0, "Panic" ],
[ 2019, 6, 29, 15, 0, "The Stunned Guys" ],
[ 2019, 6, 29, 16, 0, "Uzi" ],
[ 2019, 6, 29, 17, 0, "DJ DUNE" ],
[ 2019, 6, 29, 18, 0, "Mental Theo" ],
[ 2019, 6, 29, 19, 0, "The Darkraver & Vince" ],
[ 2019, 6, 29, 20, 0, "Buzz Fuzz" ],
[ 2019, 6, 29, 21, 30, "Frantic Freak" ],
[ 2019, 6, 29, 23, 0, "Nothing" ],
[ 2019, 6, 30, 11, 0, "Sequence & Ominous" ],
[ 2019, 6, 30, 12, 30, "Ophidian & Ruffneck" ],
[ 2019, 6, 30, 14, 0, "Re-Style" ],
[ 2019, 6, 30, 15, 0, "Art of Fighters" ],
[ 2019, 6, 30, 16, 0, "Endymion" ],
[ 2019, 6, 30, 17, 0, "Noize Suppressor" ],
[ 2019, 6, 30, 18, 0, "Synapse & Sei2ure" ],
[ 2019, 6, 30, 19, 0, "Unexist" ],
[ 2019, 6, 30, 20, 0, "Nothing" ]
],
"faq": [
]
},
{
"stage": "Silver",
"mc": "Dart",
"channel": "591928389938446356",
"emoji": ":loud_sound:",
"url": "https://www.q-dance.com/en/videos/silver-audio-live-2019",
"sets": [
[ 2019, 6, 29, 12, 0, "Mindustries" ],
[ 2019, 6, 29, 13, 0, "Manu Le Malin" ],
[ 2019, 6, 29, 14, 0, "The Clamps" ],
[ 2019, 6, 29, 15, 30, "Penta" ],
[ 2019, 6, 29, 16, 30, "Deathmachine" ],
[ 2019, 6, 29, 18, 0, "Khaoz Engine" ],
[ 2019, 6, 29, 19, 30, "Bryan Fury" ],
[ 2019, 6, 29, 20, 30, "Hellfish" ],
[ 2019, 6, 29, 22, 0, "Nothing" ],
[ 2019, 6, 30, 12, 0, "Strange Arrival" ],
[ 2019, 6, 30, 13, 0, "Somniac One" ],
[ 2019, 6, 30, 14, 0, "Katharsys" ],
[ 2019, 6, 30, 15, 0, "Ophidian as Raziel" ],
[ 2019, 6, 30, 16, 0, "The Outside Agency" ],
[ 2019, 6, 30, 17, 0, "The DJ Producer" ],
[ 2019, 6, 30, 18, 0, "The Satan" ],
[ 2019, 6, 30, 19, 0, "Akira" ],
[ 2019, 6, 30, 20, 0, "Nothing" ]
],
"faq": [
]
},
{
"stage": "Purple",
"mc": "Dash & Le Prince",
"channel": "591928410209517568",
"emoji": ":loud_sound:",
"url": "https://www.q-dance.com/en/videos/purple-audio-live-2019",
"sets": [
[ 2019, 6, 29, 11, 0, "Solstice" ],
[ 2019, 6, 29, 12, 30, "Nexone" ],
[ 2019, 6, 29, 14, 0, "Jesse Jax" ],
[ 2019, 6, 29, 15, 0, "PRDX" ],
[ 2019, 6, 29, 16, 0, "STK" ],
[ 2019, 6, 29, 17, 0, "Bestia" ],
[ 2019, 6, 29, 18, 0, "Kuzak" ],
[ 2019, 6, 29, 19, 0, "Dawnfire" ],
[ 2019, 6, 29, 20, 0, "Imperial" ],
[ 2019, 6, 29, 21, 0, "Unifire" ],
[ 2019, 6, 29, 22, 0, "Nothing" ],
[ 2019, 6, 30, 15, 0, "Charter" ],
[ 2019, 6, 30, 15, 45, "Envine" ],
[ 2019, 6, 30, 16, 30, "Helix" ],
[ 2019, 6, 30, 17, 15, "Attack" ],
[ 2019, 6, 30, 18, 0, "Stormerz" ],
[ 2019, 6, 30, 19, 0, "Emphasis" ],
[ 2019, 6, 30, 20, 0, "Nothing" ]
],
"faq": [
]
}
]

35
events/Epiq2019.json Normal file
View file

@ -0,0 +1,35 @@
[
{
"stage": "mainstage",
"channel": "319525278978277407",
"emoji": "<:epiqE:661533593356468244><:epiqP:661533593088294933><:epiqI:661533593323044874><:epiqQ:661533593343885353>",
"url": "https://www.q-dance.com/en/radio/",
"unconfirmed": false,
"streamdelay": 1,
"sets": [
[ 2019, 12, 31, 17, 45, "Q-dance Hardstyle Top 100 to 11" ],
[ 2019, 12, 31, 20, 30, "Primeshock" ],
[ 2019, 12, 31, 21, 30, "Demi Kanon" ],
[ 2019, 12, 31, 22, 15, "Sound Rush vs. Demi Kanon" ],
[ 2019, 12, 31, 22, 45, "Sound Rush" ],
[ 2019, 12, 31, 23, 40, "Hardstyle Top 10" ],
[ 2020, 1, 1, 0, 0, "Frequencerz" ],
[ 2020, 1, 1, 0, 45, "Devin Wild" ],
[ 2020, 1, 1, 1, 30, "Devin Wild vs. Rebelion" ],
[ 2020, 1, 1, 2, 0, "Psyko Punkz vs. Frequencerz" ],
[ 2020, 1, 1, 2, 30, "Psyko Punkz" ],
[ 2020, 1, 1, 3, 0, "D-Block & S-te-Fan vs. D-Sturb" ],
[ 2020, 1, 1, 4, 0, "TILT MODE" ],
[ 2020, 1, 1, 4, 10, "Rebelion" ],
[ 2020, 1, 1, 5, 0, "Act of Rage" ],
[ 2020, 1, 1, 5, 45, "Act of Rage vs. Sefa" ],
[ 2020, 1, 1, 6, 15, "Sefa" ],
[ 2020, 1, 1, 7, 0 ]
],
"responses": {
"^\\.(top100|top10)": "Find the Q-dance Top 100 here: <#661622907109244939>",
"^\\.(url|stream|link|watch)$": ":tv: Listen to the livestream in our **Q-dance Radio voice channel** or here: **<https://www.q-dance.com/en/radio/>**",
"^\\.(mc|host)$": ":microphone: The top 100 is hosted by **Tellem**. The MC during Epiq is **Villan**."
}
}
]

29
events/HardBass2019.json Normal file
View file

@ -0,0 +1,29 @@
[
{
"stage": "mainstage",
"mc": "DV8 & Villain",
"channel": "319525278978277407",
"emoji": ":blue_heart::green_heart::yellow_heart::heart:",
"url": "https://live.b2s.nl/",
"sets": [
[ 2019, 2, 9, 21, 0, "Zany" ],
[ 2019, 2, 9, 22, 0, "TEAM BLUE (Coone, D-Block & S-te-Fan, Wildstylez)" ],
[ 2019, 2, 9, 23, 30, "Headhunterz (LIVE)" ],
[ 2019, 2, 10, 0, 0, "TEAM GREEN (Atmozfears, B-Front, Noisecontrollers)" ],
[ 2019, 2, 10, 1, 30, "I AM HARDSTYLE Take Over (Brennan Heart, Code Black, Toneshifterz) (LIVE)" ],
[ 2019, 2, 10, 2, 15, "Heroes of Hard Bass show" ],
[ 2019, 2, 10, 3, 0, "TEAM YELLOW (Frequencerz, Phuture Noize, Sub Zero Project)" ],
[ 2019, 2, 10, 4, 30, "Ran-D - We Rule The Night (Showcase) (LIVE)" ],
[ 2019, 2, 10, 5, 0, "TEAM RED (E-Force, Radical Redemption, Rejecta)" ],
[ 2019, 2, 10, 6, 30, "End of Line (Warface, Delete, Killshot) (LIVE)" ],
[ 2019, 2, 10, 7, 0, "Nothing" ]
],
"faq": [
"You can remove ads by logging in to the b2s website with a b2s account (or a Q-dance account).",
"Disable your adblocker to fix common issues with the stream player.",
"There's only 1 camera for the warmup set, it'll change soon.",
"Yes, <@193774894075346944> is recording: <https://old.reddit.com/r/hardstyle/aoybih>",
"The timer is counting down until the end of Hard Bass. (hh:mm:ss.mm)"
]
}
]

23
events/Holland.json Normal file
View file

@ -0,0 +1,23 @@
[
{
"stage": "de-huiskamer",
"mc": "DV8",
"channel": "495260892535980032",
"emoji": ":flag_nl:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-stream-from-x-qlusive-holland-xxl",
"sets": [
[ 29, 22, 5, "Zany" ],
[ 29, 23, 5, "Demi Kanon" ],
[ 29, 23, 53, "Bass Modulators" ],
[ 30, 0, 50, "The Partysquad & Outsiders" ],
[ 30, 1, 48, "Coone's Inburgeringscursus" ],
[ 30, 2, 35, "Ransom & Dr. Rude" ],
[ 30, 3, 20, "Psyko Punkz" ],
[ 30, 4, 5, "Frequencerz" ],
[ 30, 5, 5, "Zazafront" ],
[ 30, 5, 35, "Bass Chaserz" ],
[ 30, 6, 20, "Dr. Peacock" ],
[ 30, 7, 5, "Nothing" ]
]
}
]

20
events/Holland2019.json Normal file
View file

@ -0,0 +1,20 @@
[
{
"stage": "mainstage",
"mc": "DV8",
"channel": "319525278978277407",
"emoji": ":flag_nl:",
"url": "https://www.q-dance.com/en/radio/",
"sets": [
[ 2019, 9, 28, 23, 0, "Retroshock (Primeshock & Retrospect)" ],
[ 2019, 9, 29, 0, 0, "Dr. Ruthless" ],
[ 2019, 9, 29, 1, 0, "Sound Rush" ],
[ 2019, 9, 29, 2, 0, "Nothing" ],
[ 2019, 9, 29, 3, 0, "Outsiders" ],
[ 2019, 9, 29, 4, 0, "De Nachtbrakers: Bass Chaserz, Degos & Re-Done, Endymion" ],
[ 2019, 9, 29, 5, 0, "Warface presenteert Warfeest" ],
[ 2019, 9, 29, 6, 0, "D-Fence" ],
[ 2019, 9, 29, 7, 0, "Nothing" ]
]
}
]

25
events/Impaqt2019.json Normal file
View file

@ -0,0 +1,25 @@
[
{
"stage": "Colossus",
"mc": "Villain",
"channel": "319525278978277407",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/radio/",
"sets": [
[ 2019, 9, 7, 14, 0, "Primeshock" ],
[ 2019, 9, 7, 15, 0, "KELTEK" ],
[ 2019, 9, 7, 16, 0, "Atmozfears & Sound Rush present: 2//\\\\\\\\1" ],
[ 2019, 9, 7, 16, 30, "Noisecontrollers & Devin Wild" ],
[ 2019, 9, 7, 17, 30, "Wildstylez" ],
[ 2019, 9, 7, 18, 30, "Sefa & Rooler" ],
[ 2019, 9, 7, 19, 0, "Zatox" ],
[ 2019, 9, 7, 20, 0, "Phuture Noize" ],
[ 2019, 9, 7, 21, 0, "D-Block & S-te-Fan" ],
[ 2019, 9, 7, 22, 0, "Ran-D" ],
[ 2019, 9, 7, 23, 0, "Warface" ],
[ 2019, 9, 8, 0, 0, "Rebelion" ],
[ 2019, 9, 8, 1, 0, "Miss K8" ],
[ 2019, 9, 8, 2, 0, "Nothing" ]
]
}
]

22
events/Mysteryland.json Normal file
View file

@ -0,0 +1,22 @@
[
{
"stage": "mysteryland",
"mc": "Villain",
"channel": "481392155022196747",
"emoji": ":bird:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-stream",
"sets": [
[ 25, 14, 0, "ANDY SVGE" ],
[ 25, 15, 30, "Ruthless" ],
[ 25, 16, 30, "Brennan Heart" ],
[ 25, 17, 30, "Code Black" ],
[ 25, 19, 0, "Nothing" ],
[ 25, 19, 33, "Wildstylez (w/o Hardwell)" ],
[ 25, 20, 0, "Atmozfears" ],
[ 25, 21, 0, "B-Front" ],
[ 25, 22, 0, "Sub Zero Project" ],
[ 25, 22, 50, "Endshow" ],
[ 25, 23, 0, "Nothing" ]
]
}
]

112
events/Qbase2018.json Normal file
View file

@ -0,0 +1,112 @@
[
{
"stage": "openair",
"mc": "DV8",
"channel": "487970506910203914",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-open-air",
"sets": [
[ 8, 17, 0, "Retrospect" ],
[ 8, 18, 30, "Adrenalize" ],
[ 8, 20, 0, "KELTEK" ],
[ 8, 21, 0, "D-Block & S-te-Fan" ],
[ 8, 22, 0, "Sound Rush" ],
[ 8, 23, 0, "Coone" ],
[ 9, 0, 0, "Atmozfears" ],
[ 9, 1, 30, "Noisecontrollers" ],
[ 9, 3, 0, "Ran-D & B-Front" ],
[ 9, 4, 0, "Nothing" ],
[ 9, 6, 30, "Sefa" ],
[ 9, 7, 0, "Nothing" ]
]
},
{
"stage": "hangar",
"mc": "Livid & Nolz",
"channel": "487970523784019979",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-hangar",
"sets": [
[ 8, 17, 0, "D-Attack" ],
[ 8, 18, 30, "Rejecta" ],
[ 8, 20, 0, "Myst vs. Degos & Re-Done" ],
[ 8, 21, 30, "Deetox" ],
[ 8, 22, 45, "Adaro" ],
[ 9, 0, 0, "Frequencerz" ],
[ 9, 1, 0, "Bass Chaserz" ],
[ 9, 2, 0, "Nothing" ],
[ 9, 2, 30, "E-Force" ],
[ 9, 4, 0, "Nothing" ],
[ 9, 5, 0, "Rebelion" ],
[ 9, 6, 0, "Jason Payne & Apexx" ],
[ 9, 7, 0, "Nothing" ]
]
},
{
"stage": "thunderdome",
"mc": "Da Mouth of Madness",
"channel": "488016911838347276",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-thunderdome",
"sets": [
[ 8, 17, 0, "Waxweazle" ],
[ 8, 18, 30, "Vince" ],
[ 8, 20, 0, "MD&A" ],
[ 8, 20, 30, "Predator" ],
[ 8, 22, 0, "Evil Activities" ],
[ 8, 23, 30, "Nosferatu" ],
[ 9, 0, 30, "The Melodyst" ],
[ 9, 1, 30, "Promo" ],
[ 9, 3, 0, "I:GOR" ],
[ 9, 4, 0, "Unexist" ],
[ 9, 5, 0, "Andy The Core & Lady Dammage" ],
[ 9, 6, 0, "Spitnoise" ],
[ 9, 7, 0, "Nothing" ]
]
},
{
"stage": "bkjn",
"mc": "No-ID & RG",
"channel": "488018562124873741",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-bkjn",
"sets": [
[ 8, 18, 0, "Hard Effectz vs. Stampede" ],
[ 8, 19, 0, "Hyrule War vs. Mr. Ivex" ],
[ 8, 20, 0, "Cryogenic" ],
[ 8, 21, 0, "Para Italia vs. Aggressive" ],
[ 8, 22, 0, "Super Trash Bros [LIVE]" ],
[ 8, 22, 30, "Rob Gee & Lunatic present: BKJN Music Showcase" ],
[ 8, 23, 45, "Sefa" ],
[ 9, 0, 15, "Vandalism" ],
[ 9, 1, 0, "Repix" ],
[ 9, 2, 0, "Omkara [LIVE]" ],
[ 9, 2, 30, "Dr. Peacock" ],
[ 9, 3, 30, "Chaotic Hostility & Chrono" ],
[ 9, 4, 30, "The Vizitor vs. MBK" ],
[ 9, 5, 30, "ELITE [LIVE]" ],
[ 9, 6, 0, "Nothing"]
]
},
{
"stage": "evolution",
"mc": "DL",
"channel": "488019382094528512",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-evolution",
"sets": [
[ 8, 20, 0, "The Pitcher & Retrospect" ],
[ 8, 21, 45, "Coone & Ruthless" ],
[ 8, 22, 30, "Nightbreed Crew" ],
[ 9, 0, 0, "DJ The Prophet & Devin Wild" ],
[ 9, 1, 0, "Deetox & Sound Rush" ],
[ 9, 2, 30, "D-Fence & Panic" ],
[ 9, 3, 45, "Rooler" ],
[ 9, 5, 0, "Nothing" ]
]
}
]

View file

@ -0,0 +1,30 @@
[
{
"stage": "qbase",
"mc": "DV8 & Livid & Nolz",
"channel": "487537296581722112",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/radio/",
"sets": [
[ 8, 17, 0, "Retrospect / D-Attack" ],
[ 8, 18, 30, "Adrenalize / Rejecta" ],
[ 8, 20, 0, "KELTEK / Myst vs. Degos & Re-Done" ],
[ 8, 21, 0, "D-Block & S-te-Fan / Myst vs. Degos & Re-Done" ],
[ 8, 21, 30, "D-Block & S-te-Fan / Deetox" ],
[ 8, 22, 0, "Sound Rush / Deetox" ],
[ 8, 22, 45, "Sound Rush / Adaro" ],
[ 8, 23, 0, "Coone / Adaro" ],
[ 9, 0, 0, "Atmozfears / Frequencerz" ],
[ 9, 1, 0, "Atmozfears / Bass Chaserz" ],
[ 9, 1, 30, "Noisecontrollers / Bass Chaserz" ],
[ 9, 2, 0, "Noisecontrollers" ],
[ 9, 2, 30, "Noisecontrollers / E-Force" ],
[ 9, 3, 0, "Ran-D & B-Front / E-Force" ],
[ 9, 4, 0, "???" ],
[ 9, 5, 0, "Rebelion" ],
[ 9, 6, 0, "Jason Payne & Apexx" ],
[ 9, 6, 30, "Sefa / Jason Payne & Apexx" ],
[ 9, 7, 0, "Nothing" ]
]
}
]

46
events/Qbase2018_Two.json Normal file
View file

@ -0,0 +1,46 @@
[
{
"stage": "openair",
"mc": "DV8",
"channel": "487970506910203914",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-open-air",
"sets": [
[ 8, 17, 0, "Retrospect" ],
[ 8, 18, 30, "Adrenalize" ],
[ 8, 20, 0, "KELTEK" ],
[ 8, 21, 0, "D-Block & S-te-Fan" ],
[ 8, 22, 0, "Sound Rush" ],
[ 8, 23, 0, "Coone" ],
[ 9, 0, 0, "Atmozfears" ],
[ 9, 1, 30, "Noisecontrollers" ],
[ 9, 3, 0, "Ran-D & B-Front" ],
[ 9, 4, 0, "Nothing" ],
[ 9, 6, 30, "Sefa" ],
[ 9, 7, 0, "Nothing" ]
]
},
{
"stage": "hangar",
"mc": "Livid & Nolz",
"channel": "487970523784019979",
"emoji": ":airplane:",
"url": "https://www.q-dance.com/en/videos/q-dance-live-hangar",
"sets": [
[ 8, 17, 0, "D-Attack" ],
[ 8, 18, 30, "Rejecta" ],
[ 8, 20, 0, "Myst vs. Degos & Re-Done" ],
[ 8, 21, 30, "Deetox" ],
[ 8, 22, 45, "Adaro" ],
[ 9, 0, 0, "Frequencerz" ],
[ 9, 1, 0, "Bass Chaserz" ],
[ 9, 2, 0, "Nothing" ],
[ 9, 2, 30, "E-Force" ],
[ 9, 4, 0, "Nothing" ],
[ 9, 5, 0, "Rebelion" ],
[ 9, 6, 0, "Jason Payne & Apexx" ],
[ 9, 7, 0, "Nothing" ]
]
}
]

23
events/Qlimax2018.json Normal file
View file

@ -0,0 +1,23 @@
[
{
"stage": "mainstage",
"mc": "Villain",
"channel": "319525278978277407",
"emoji": ":flag_nl:",
"url": "https://www.q-dance.com/en/",
"sets": [
[ 24, 21, 30, "Luna" ],
[ 24, 22, 30, "Sound Rush" ],
[ 24, 23, 30, "Coone" ],
[ 25, 0, 30, "Bass Modulators" ],
[ 25, 1, 30, "Wildstylez (Live)" ],
[ 25, 2, 1, "Sub Zero Project" ],
[ 25, 3, 16, "Tweekacore" ],
[ 25, 4, 1, "Phuture Noize" ],
[ 25, 5, 1, "Sub Zero Project & Phuture Noize" ],
[ 25, 5, 16, "B-Freqz" ],
[ 25, 6, 1, "Dr. Peacock" ],
[ 25, 7, 0, "Nothing" ]
]
}
]

31
events/Qlimax2019.json Normal file
View file

@ -0,0 +1,31 @@
[
{
"stage": "mainstage",
"channel": "319525278978277407",
"emoji": "<:qlimax:438815681706721281>",
"url": "https://www.q-dance.com/en/events/qlimax/qlimax-2019/live",
"unconfirmed": false,
"streamdelay": 4,
"sets": [
[ 2019, 11, 23, 21, 45, "The Qreator" ],
[ 2019, 11, 23, 22, 45, "KELTEK" ],
[ 2019, 11, 23, 23, 30, "Sound Rush" ],
[ 2019, 11, 24, 0, 30, "B-Front" ],
[ 2019, 11, 24, 0, 40, "D-Block & S-te-Fan" ],
[ 2019, 11, 24, 1, 30, "Headhunterz" ],
[ 2019, 11, 24, 2, 30, "B-Front" ],
[ 2019, 11, 24, 3, 30, "Ran-D \"We Rule The Night\"" ],
[ 2019, 11, 24, 4, 15, "D-Sturb" ],
[ 2019, 11, 24, 5, 0, "Rejecta (LIVE)" ],
[ 2019, 11, 24, 5, 30, "Radical Redemption" ],
[ 2019, 11, 24, 6, 15, "Miss K8" ],
[ 2019, 11, 24, 7, 0 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Watch the livestream here: **<https://www.q-dance.com/en/events/qlimax/qlimax-2019/live>** (Note: This is a paid premium livestream!)",
"^\\.(mc|host)$": ":microphone: The MC is **Villan**, and **Nolz** for the final 2 sets",
"^\\.restream$": "The Qlimax stream is a **premium** livestream. As such, we consider all unofficial free re-streams as piracy. Do not post or ask for restreams.",
"^\\.qreator$": "It is not confirmed yet who *The Qreator* is. It was not revealed during their set."
}
}
]

29
events/Qonnect.json Normal file
View file

@ -0,0 +1,29 @@
[
{
"stage": "The Studio",
"channel": "319525278978277407",
"emoji": "<:qdance:328585093553586176>",
"url": "https://www.q-dance.com/en/videos/livestream-qonnect",
"unconfirmed": false,
"streamdelay": 3,
"sets": [
[ 2020, 3, 21, 13, 0, "Demi Kanon" ],
[ 2020, 3, 21, 14, 0, "Primeshock" ],
[ 2020, 3, 21, 15, 0, "Clockartz" ],
[ 2020, 3, 21, 16, 0, "Adrenalize" ],
[ 2020, 3, 21, 16, 45, "Audiotricz" ],
[ 2020, 3, 21, 17, 50, "Outsiders" ],
[ 2020, 3, 21, 18, 45, "Sound Rush" ],
[ 2020, 3, 21, 19, 45, "Audiofreq" ],
[ 2020, 3, 21, 20, 0, "Headhunterz" ],
[ 2020, 3, 21, 21, 0, "Devin Wild" ],
[ 2020, 3, 21, 22, 10, "Hard Driver" ],
[ 2020, 3, 21, 23, 10, "Dr. Peacock" ],
[ 2020, 3, 21, 23, 40 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://www.q-dance.com/en/videos/livestream-qonnect>**",
"^\\.(mc|host)$": ":microphone: The hosts between 13:00 and 19:00 CET are **Tellem** and **Adrenalize**. Between 19:00 and 23:00 CET it's **E-Life** and **Audiofreq**."
}
}
]

33
events/Qonnect2.json Normal file
View file

@ -0,0 +1,33 @@
[
{
"stage": "The Studio",
"channel": "319525278978277407",
"emoji": "<:qdance:328585093553586176>",
"url": "https://live.q-dance.com/",
"unconfirmed": false,
"streamdelay": 0,
"sets": [
[ 2020, 4, 4, 18, 5, "Degos & Re-Done" ],
[ 2020, 4, 4, 18, 35, "MYST" ],
[ 2020, 4, 4, 19, 15, "Hard Driver" ],
[ 2020, 4, 4, 19, 45, "Apexx" ],
[ 2020, 4, 4, 20, 20, "B-Front" ],
[ 2020, 4, 4, 20, 55, "Adaro" ],
[ 2020, 4, 4, 21, 30, "Bloodlust" ],
[ 2020, 4, 4, 22, 5, "Vertile" ],
[ 2020, 4, 4, 22, 35, "Rejecta" ],
[ 2020, 4, 4, 23, 15, "The Qreator: The Ultimate QAPITAL Mix" ],
[ 2020, 4, 4, 23, 45, "Act of Rage" ],
[ 2020, 4, 5, 0, 20, "Rebelion" ],
[ 2020, 4, 5, 0, 50, "Rooler" ],
[ 2020, 4, 5, 1, 25, "Invector" ],
[ 2020, 4, 5, 1, 55, "Thyron" ],
[ 2020, 4, 5, 2, 25, "Imperatorz" ],
[ 2020, 4, 5, 3, 0 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://live.q-dance.com/>**",
"^\\.(mc|host)$": ":microphone: The hosts are Tellem & Livid."
}
}
]

29
events/Qonnect3.json Normal file
View file

@ -0,0 +1,29 @@
[
{
"stage": "The Studio",
"channel": "319525278978277407",
"emoji": "<:qdance:328585093553586176>",
"url": "https://live.q-dance.com/",
"unconfirmed": false,
"streamdelay": 3,
"sets": [
[ 2020, 4, 11, 13, 0, "Retrospect" ],
[ 2020, 4, 11, 14, 0, "Villain" ],
[ 2020, 4, 11, 15, 0, "Coone" ],
[ 2020, 4, 11, 16, 0, "Da Tweekaz" ],
[ 2020, 4, 11, 17, 0, "Zatox" ],
[ 2020, 4, 11, 18, 0, "Darkraver (Vinyl Mix at Six)" ],
[ 2020, 4, 11, 19, 0, "KELTEK" ],
[ 2020, 4, 11, 20, 0, "Headhunterz" ],
[ 2020, 4, 11, 21, 0, "Atmozfears" ],
[ 2020, 4, 11, 22, 0, "Crypsis" ],
[ 2020, 4, 11, 23, 0, "Sefa (LIVE)" ],
[ 2020, 4, 12, 0, 0 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://live.q-dance.com/>**",
"^\\.(mc|host)$": ":microphone: The hosts between 13:00 and 18:00 are Adrenalize and Tellem. During 18:00 and 23:30 it's Audiofreq & E-life.",
"^\\.(hidechat|removechat|fuckchat)$": ":thinking: Press F12 and paste this into the console to hide the chat on live.q-dance.com: ```sc=document.getElementById(\"scrollContainer\");sc.classList.remove(\"col-l--9\");sc.classList.remove(\"col-m--8\");sc.classList.add(\"col-l--12\");sc.classList.remove(\"col-m--12\");```"
}
}
]

26
events/Reverze2019.json Normal file
View file

@ -0,0 +1,26 @@
[
{
"stage": "sportpaleis",
"mc": "Villain & Chucky",
"channel": "319525278978277407",
"emoji": ":fire:",
"url": "https://www.youtube.com/user/bassevents/live",
"sets": [
[ 2019, 2, 23, 21, 30, "Sound Rush" ],
[ 2019, 2, 23, 22, 30, "Sephyx vs Devin Wild" ],
[ 2019, 2, 23, 23, 30, "Refuzion (recorded from 20:30)" ],
[ 2019, 2, 24, 0, 0, "Headhunterz" ],
[ 2019, 2, 24, 1, 0, "KELTEK (LIVE)" ],
[ 2019, 2, 24, 1, 30, "Coone & Brennan Heart" ],
[ 2019, 2, 24, 2, 30, "Tweekacore (LIVE)" ],
[ 2019, 2, 24, 3, 0, "Mark with a K & Warface (LIVE)" ],
[ 2019, 2, 24, 3, 30, "Reverze Flashback by Dark-E & Pat B" ],
[ 2019, 2, 24, 4, 15, "Phuture Noize \"Black Mirror Society\" (LIVE)" ],
[ 2019, 2, 24, 4, 45, "Nothing" ]
],
"faq": [
"D-Block & S-te-Fan's \"Ghost Stories\" set is not livestreamed. It is instead replaced with the first half of Refuzion's set.",
"Yes, someone is definitely recording this. Including Bass Events."
]
}
]

29
events/Reverze2020.json Normal file
View file

@ -0,0 +1,29 @@
[
{
"stage": "sportpaleis",
"channel": "319525278978277407",
"emoji": "<:bassevents:685859019973460136>",
"url": "https://www.youtube.com/watch?v=QP0AGJ0avRs",
"unconfirmed": false,
"streamdelay": 3,
"sets": [
[ 2020, 3, 7, 21, 0, "Refuzion" ],
[ 2020, 3, 7, 21, 30, "Mandy" ],
[ 2020, 3, 7, 22, 15, "Keltek vs. Sound Rush" ],
[ 2020, 3, 7, 23, 15, "Psyko Punkz" ],
[ 2020, 3, 8, 0, 0, "Brennan Heart: 15 Years Reverze Special" ],
[ 2020, 3, 8, 0, 45, "The Elite", "Da Tweekaz, Coone, Hard Driver" ],
[ 2020, 3, 8, 1, 45, "D-Block & S-te-Fan" ],
[ 2020, 3, 8, 2, 45, "Sub Zero Project: Rave Into Space (LIVE)" ],
[ 2020, 3, 8, 3, 15, "15 Years Reverze Flashback", "Pat B, Dark-E, Mark with a K & MC Chucky" ],
[ 2020, 3, 8, 4, 0, "Warface & D-Sturb: Synchronised" ],
[ 2020, 3, 8, 4, 45, "Minus Militia: The Code of Conduct (LIVE)" ],
[ 2020, 3, 8, 5, 15 ]
],
"responses": {
"^\\.(url|stream|link|watch)$": ":tv: Tune in to the livestream here: **<https://www.youtube.com/watch?v=QP0AGJ0avRs>**",
"^\\.(mc|host)$": ":microphone: The MC is **Villain**.",
"^\\.(lotto|arena|stages)$": ":warning: **ONLY** the Sportpaleis is being streamed, the Lotto Arena stage is **NOT** being streamed."
}
}
]

View file

@ -0,0 +1,47 @@
[
{
"stage": "tomorrowland",
"mc": "Villain",
"channel": "469913276592029706",
"emoji": ":one:",
"url": "https://live.tomorrowland.com/",
"sets": [
[ 21, 13, 0, "Demi Kanon" ],
[ 21, 14, 0, "Mandy & Adrenalize" ],
[ 21, 15, 0, "Audiotricz" ],
[ 21, 16, 0, "Da Tweekaz" ],
[ 21, 17, 0, "Wildstylez" ],
[ 21, 18, 0, "Brennan Heart" ],
[ 21, 19, 0, "Atmozfears" ],
[ 21, 20, 0, "Zatox" ],
[ 21, 21, 0, "Ran-D" ],
[ 21, 22, 0, "Sub Zero Project" ],
[ 21, 23, 0, "Korsakoff" ],
[ 22, 0, 0, "Nothing" ],
[ 28, 12, 0, "Ransom" ],
[ 28, 13, 0, "Refuzion" ],
[ 28, 14, 0, "Sound Rush" ],
[ 28, 15, 0, "Wasted Penguinz" ],
[ 28, 16, 0, "Mark With a K & MC Chucky" ],
[ 28, 17, 0, "Bass Modulators" ],
[ 28, 18, 0, "Code Black" ],
[ 28, 19, 0, "Noisecontrollers" ],
[ 28, 20, 0, "Psyko Punkz" ],
[ 28, 21, 0, "Frequencerz" ],
[ 28, 22, 0, "B-Front" ],
[ 28, 23, 0, "Partyraiser" ],
[ 29, 0, 0, "Nothing" ],
[ 29, 16, 0, "Da Tweekaz" ],
[ 29, 17, 0, "Wildstylez" ],
[ 29, 18, 0, "Coone" ],
[ 29, 19, 0, "Pablo Discobar" ],
[ 29, 19, 30, "Darren Styles" ],
[ 29, 20, 15, "Zatox" ],
[ 29, 21, 15, "Coone vs. Hard Driver" ],
[ 29, 22, 15, "Gunz for Hire" ],
[ 29, 23, 0, "Nothing" ]
]
}
]

21
events/Tweekaz.json Normal file
View file

@ -0,0 +1,21 @@
[
{
"stage": "mainstage",
"mc": "Villain",
"channel": "319525278978277407",
"emoji": ":duck::wine_glass:",
"url": "https://www.q-dance.com/en/radio/",
"sets": [
[ 2019, 1, 26, 23, 0, "Sephyx & Refuzion" ],
[ 2019, 1, 27, 0, 0, "Code Black" ],
[ 2019, 1, 27, 1, 0, "Da Tweekaz" ],
[ 2019, 1, 27, 2, 0, "Coone, Hard Driver & Da Tweekaz \"Dirty Workz Elite\"" ],
[ 2019, 1, 27, 3, 15, "D-Block & S-te-Fan" ],
[ 2019, 1, 27, 4, 15, "Tweekacore" ],
[ 2019, 1, 27, 4, 45, "Sub Zero Project vs. Da Tweekaz" ],
[ 2019, 1, 27, 5, 30, "D-Sturb" ],
[ 2019, 1, 27, 6, 15, "Destructive Tendencies vs. Da Tweekaz" ],
[ 2019, 1, 27, 7, 0, "Nothing" ]
]
}
]

28
events/WOWWOW2018.json Normal file
View file

@ -0,0 +1,28 @@
[
{
"stage": "mainstage",
"mc": "Villain",
"channel": "319525278978277407",
"emoji": ":champagne::tada:",
"url": "https://www.q-dance.com/en/radio/",
"sets": [
[ 2018, 12, 31, 21, 0, "Max Enforcer" ],
[ 2018, 12, 31, 22, 0, "Mark with a K presents: Belgium" ],
[ 2018, 12, 31, 23, 0, "KELTEK: Best euphoric breakthrough" ],
[ 2018, 12, 31, 23, 40, "Hardstyle Top 10" ],
[ 2019, 1, 1, 0, 0, "Brennan Heart" ],
[ 2019, 1, 1, 0, 45, "Best of X-Qlusive Frequencerz" ],
[ 2019, 1, 1, 1, 30, "POWER HOUR in 10 minutes" ],
[ 2019, 1, 1, 1, 40, "Noisecontrollers & Audiotricz presents: Spirit of Hardstyle" ],
[ 2019, 1, 1, 2, 20, "Atmozfears: Q-BASE anthem 2018" ],
[ 2019, 1, 1, 3, 0, "Ran-D presents: Roughstate 2018" ],
[ 2019, 1, 1, 3, 30, "ZazaFront: X-Qlusive Holland set" ],
[ 2019, 1, 1, 4, 0, "Sub Zero Project: Qlimax anthem 2018" ],
[ 2019, 1, 1, 4, 15, "Rejecta: Best RAW Talent" ],
[ 2019, 1, 1, 4, 45, "Phuture Noize: QAPITAL anthem 2018" ],
[ 2019, 1, 1, 5, 15, "Rebelion: QAPITAL anthem 2019" ],
[ 2019, 1, 1, 6, 0, "Partyraiser presents: Hardcore 2018" ],
[ 2019, 1, 1, 7, 0, "Nothing" ]
]
}
]

4
index.js Normal file
View file

@ -0,0 +1,4 @@
var Startup = require("./Startup");
let startup = new Startup();
startup.run();

29
modules/autoreact.js Normal file
View file

@ -0,0 +1,29 @@
const discord = require("discord.js");
class AutoReactModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
this.bot = bot;
}
/**
* @param {discord.Message} msg
* @param {Boolean} edited
*/
onMessage(msg, edited)
{
if (edited) {
return;
}
if (msg.content.match(new RegExp(this.config.match, "i"))) {
msg.react(this.config.emoji);
}
}
}
module.exports = AutoReactModule;

513
modules/event.js Normal file
View file

@ -0,0 +1,513 @@
var fs = require("fs");
var moment = require("moment");
var cmdsplit = require("./../cmdsplit");
class EventSchedule
{
constructor(config, client, bot)
{
this.event = config;
this.client = client;
this.bot = bot;
this.lastNotFound = new Date(1970, 1, 1);
this.lastNow = new Date(1970, 1, 1);
this.loadSchedule(this.event.file);
}
loadSchedule(filename)
{
console.log('Loading schedule: "' + filename + '"');
this.schedule = JSON.parse(fs.readFileSync(filename));
for (var i = 0; i < this.schedule.length; i++) {
let stage = this.schedule[i];
console.log("Event channel: " + stage.channel);
this.client.channels.fetch(stage.channel).then(channel => {
stage.channel = channel;
this.updateChannel(stage);
}).catch(() => {
console.error("Unable to find channel for stage \"" + stage.stage + "\"");
});
var newResponses = [];
for (var expression in stage.responses) {
let newResponse = {
match: new RegExp(expression, "i"),
msg: stage.responses[expression]
};
newResponses.push(newResponse);
}
stage.responses = newResponses;
stage.channelExtra = null;
if (stage.extra_channel !== undefined) {
this.client.channels.fetch(stage.extra_channel).then(channel => {
stage.channelExtra = channel;
}).catch(() => {
console.error("Unable to find channel for stage \"" + stage.stage + "\"");
});
}
var streamDelay = stage.streamdelay;
for (var j = 0; j < stage.sets.length; j++) {
var set = stage.sets[j];
var dateArray = set.slice(0, 5);
dateArray[1] -= 1; // months are 0-indexed, for some reason. even in the moment library!
var setDate = moment(dateArray).add(streamDelay, 'm');
var newSet = {
date: setDate,
name: set[5],
report: moment() > setDate,
report_5min: moment().add(5, 'm') > setDate,
nothing: (set[5] === undefined || set[5] == "Nothing"),
who: set[6]
};
stage.sets[j] = newSet;
}
}
}
getStage(stage)
{
for (var i = 0; i < this.schedule.length; i++) {
var s = this.schedule[i];
if (s.stage == stage) {
return s;
}
}
return null;
}
getStageByChannel(channel)
{
for (var i = 0; i < this.schedule.length; i++) {
var s = this.schedule[i];
if (s.channel == channel) {
return s;
}
}
return null;
}
findSets(query)
{
query = query.toLowerCase();
var ret = [];
for (var i = 0; i < this.schedule.length; i++) {
var stage = this.schedule[i];
for (var j = 0; j < stage.sets.length; j++) {
var set = stage.sets[j];
if (set.nothing) {
continue;
}
if (set.name.toLowerCase().indexOf(query) != -1 || (set.who && set.who.toLowerCase().indexOf(query) != -1)) {
ret.push({
set: set,
stage: stage
});
}
}
}
return ret;
}
getCurrentSet(stage)
{
var date = new Date();
for (var i = 0; i < stage.sets.length; i++) {
var set = stage.sets[i];
if (date < set.date) {
if (i == 0) {
// Happens if no set has started yet on this stage
return null;
}
return stage.sets[i - 1];
}
}
// Happens if the last set has been played
return stage.sets[stage.sets.length - 1];
}
getNextSet(stage)
{
var date = new Date();
for (var i = 0; i < stage.sets.length; i++) {
var set = stage.sets[i];
if (date < set.date) {
return set;
}
}
// Happens if this is the final set on this stage
return null;
}
getNextLiveSet(stage)
{
var date = new Date();
for (var i = 0; i < stage.sets.length; i++) {
var set = stage.sets[i];
if (date < set.date && !set.nothing) {
return set;
}
}
// Happens if this is the final set on this stage
return null;
}
onTick()
{
var date = moment();
for (var i = 0; i < this.schedule.length; i++) {
var stage = this.schedule[i];
var current = this.getCurrentSet(stage);
if (current !== null) {
if (!current.report) {
current.report = true;
if (!current.nothing) {
console.log("Starting now: " + current.name);
var msg = ":red_circle: STARTING NOW: **" + current.name + "**";
stage.channel.send(msg);
if (stage.channelExtra) {
stage.channelExtra.send(msg);
}
} else {
console.log("Stream is not live anymore.");
var next = this.getNextSet(stage);
if (next !== null && !next.nothing) {
var msg = ":no_entry_sign: Stream is no longer live. Next set is at **" + this.getTimeString(next.date) + "**!";
stage.channel.send(msg);
if (stage.channelExtra) {
stage.channelExtra.send(msg);
}
} else {
var msg = ":tada: This is the end of the livestream. Thanks for watching.";
stage.channel.send(msg);
if (stage.channelExtra) {
stage.channelExtra.send(msg);
}
}
}
this.updateChannel(stage);
}
}
var next = this.getNextSet(stage);
if (next !== null && !next.nothing) {
if (date.clone().add(5, 'm') > next.date && !next.report_5min) {
next.report_5min = true;
console.log("Starting in 5 minutes: " + next.name);
var msg = ":warning: **" + next.name + "** starts in 5 minutes!";
stage.channel.send(msg);
if (stage.channelExtra) {
stage.channelExtra.send(msg);
}
}
}
}
}
onCmdCurrent(msg) { this.onCmdNp(msg); }
onCmdNow(msg) { this.onCmdNp(msg); }
onCmdNp(msg)
{
var stage = this.getStageByChannel(msg.channel);
if (!stage) {
return;
}
var current = this.getCurrentSet(stage);
if (current !== null && !current.nothing) {
if (current.who) {
msg.channel.send(":red_circle: Now playing: **" + current.name + "**, started <t:" + current.date.unix() + ":R>! (" + current.who + ")");
} else {
msg.channel.send(":red_circle: Now playing: **" + current.name + "**, started <t:" + current.date.unix() + ":R>!");
}
} else {
msg.channel.send("Nobody's playing right now.");
}
}
onCmdNext(msg)
{
var stage = this.getStageByChannel(msg.channel);
if (!stage) {
return;
}
var next = this.getNextSet(stage);
if (next) {
var localTime = "**" + this.getTimeString(next.date) + "**";
localTime += " (<t:" + next.date.unix() + ":R>)";
if (next.name) {
if (next.who) {
msg.channel.send(":arrow_forward: Next up: **" + next.name + "**, at " + localTime + " (" + next.who + ")");
} else {
msg.channel.send(":arrow_forward: Next up: **" + next.name + "**, at " + localTime);
}
} else {
msg.channel.send(":arrow_forward: The stream ends at " + localTime);
}
} else {
msg.channel.send("There's nothing playing next.");
}
}
getScheduleString(stage, limit, starttime)
{
var ret = "";
if (stage.unconfirmed) {
ret = ":warning: **Note:** Set times are not confirmed!\n";
}
if (!starttime) {
starttime = moment(stage.sets[0].date).clone().subtract(1, 'm');
}
var lines = 0;
for (var i = 0; i < stage.sets.length; i++) {
var set = stage.sets[i];
if (starttime > set.date) {
continue;
}
if (limit && lines == limit) {
ret += "(limited, use `.fullschedule` for the full schedule)\n";
break;
}
lines++;
if (set.nothing) {
ret += "- <t:" + set.date.unix() + ":t> (<t:" + set.date.unix() + ":R>), the stream will be offline :no_entry_sign:\n";
} else {
if (set.who) {
ret += "- <t:" + set.date.unix() + ":t> (<t:" + set.date.unix() + ":R>): **" + set.name + "** (" + set.who + ")\n";
} else {
ret += "- <t:" + set.date.unix() + ":t> (<t:" + set.date.unix() + ":R>): **" + set.name + "**\n";
}
}
}
if (lines == 0) {
ret = "We have nothing left! :frowning:";
} else if (limit) {
ret = ":calendar_spiral: Next " + limit + " sets are:\n" + ret.trim();
} else {
ret = ":calendar_spiral: The full schedule:\n" + ret.trim();
}
return ret;
}
onCmdTimetable(msg) { this.onCmdSchedule(msg); }
onCmdSched(msg) { this.onCmdSchedule(msg); }
onCmdSchedule(msg)
{
var stage = this.getStageByChannel(msg.channel);
if (!stage) {
return;
}
msg.channel.send(this.getScheduleString(stage, 5, moment()));
}
onCmdFullSched(msg) { this.onCmdFullSchedule(msg); }
onCmdFullSchedule(msg)
{
var stage = this.getStageByChannel(msg.channel);
if (!stage) {
return;
}
var text = this.getScheduleString(stage);
while (text.length > 0) {
msg.author.send(text.substr(0, 2000)).catch(console.error);
text = text.substr(2000);
}
msg.reply("I've DM'd you the full schedule.");
}
onCmdFind(msg)
{
var query = Array.from(arguments).slice(1).join(" ").trim();
if (query.length < 3) {
return;
}
var results = this.findSets(query);
var ret = "";
if (results.length == 0) {
ret = "I found nothing :frowning:";
// Avoid spamming "I found nothing" when jokers do .find a meaning of life
var now = new Date();
if ((now - this.lastNotFound) < 60 * 1000) {
return;
}
this.lastNotFound = now;
} else {
var date = new Date();
for (var i = 0; i < results.length; i++) {
var res = results[i];
var localTime = "**" + this.getTimeString(res.set.date) + "**";
localTime += " (<t:" + res.set.date.unix() + ":R>)";
var stageMessage = "";
if (this.schedule.length > 1) {
stageMessage = " on **" + res.stage.stage + "** " + res.stage.emoji + " stage!";
}
if (date > res.date) {
ret += res.set.name + " already played on <t:" + res.set.date.unix() + ":F>\n";
} else {
ret += res.set.name + " plays on <t:" + res.set.date.unix() + ":F>\n";
}
}
}
msg.channel.send(":calendar_spiral: " + ret.trim());
}
onCmdReloadSchedule(msg)
{
if (!this.bot.isAdmin(msg.member)) {
return;
}
this.loadSchedule(this.event.file);
msg.reply("schedule reloaded!");
}
onMessage(msg)
{
var inStage = false;
for (var i = 0; i < this.schedule.length; i++) {
var stage = this.schedule[i];
if (stage.channel.id != msg.channel.id) {
continue;
}
inStage = true;
for (var j = 0; j < stage.responses.length; j++) {
var r = stage.responses[j];
var match = msg.content.match(r.match);
if (match) {
var sendMessage = r.msg;
for (var k = 0; k < match.length; k++) {
sendMessage = sendMessage.replace("$" + k, match[k]);
}
msg.channel.send(sendMessage);
return true;
}
}
}
var isCommand = msg.content.startsWith(".");
var parse = [];
if (isCommand) {
parse = cmdsplit(msg.content);
}
// Outside-channel schedule command
if (isCommand && this.schedule.length > 1 && !inStage && (parse[0] == ".schedule" || parse[0] == ".timetable" || parse[0] == ".sched" || parse[0] == ".current" || parse[0] == ".now")) {
// Avoid spamming long .now message when jokers spam .now
var now = new Date();
if ((now - this.lastNow) < 60 * 1000) {
return true;
}
this.lastNow = now;
var ret = "**LIVE**\n";
for (var i = 0; i < this.schedule.length; i++) {
var stage = this.schedule[i];
var current = this.getCurrentSet(stage);
var next = this.getNextSet(stage);
if (current === null && next !== null && next.date.date() != moment(now).date()) {
continue;
}
if (current !== null && !current.nothing) {
ret += stage.emoji + " " + stage.stage + ": **" + current.name + "**";
} else {
ret += stage.emoji + " " + stage.stage + ": Not live";
}
if (next !== null && !next.nothing) {
ret += ", next: " + next.name;
} else {
ret += ".";
}
ret += " " + stage.channel.toString() + "\n";
}
msg.channel.send(ret.trim());
return true;
}
return false;
}
getTimeString(date)
{
return "<t:" + date.unix() + ":t>";
}
updateChannel(stage)
{
if (typeof(stage.channel) == "string") {
return;
}
var line = "";
var current = this.getCurrentSet(stage);
var next = this.getNextLiveSet(stage);
if ((current === null || current.nothing) && next === null) {
line += " :tada: Thanks for watching.";
} else {
if (current !== null && !current.nothing) {
line += " __" + current.name + "__ <t:" + current.date.unix() + ":R>";
} else {
line += " :no_entry_sign: __Not currently live__.";
}
if (next !== null) {
line += " :arrow_forward: Next: __" + next.name + "__ <t:" + next.date.unix() + ":R>";
} else {
line += " :warning: This is the last set!";
}
line += " :link: " + stage.url;
}
stage.channel.setTopic(stage.emoji + " " + line, "Automated bot action for event");
}
}
module.exports = EventSchedule;

133
modules/eventquick.js Normal file
View file

@ -0,0 +1,133 @@
class EventQuickModule
{
constructor(config, client, bot)
{
this.event = config;
this.client = client;
this.bot = bot;
this.current = '';
this.ended = false;
if (this.event.current !== undefined) {
this.current = this.event.current;
}
this.channel = client.channels.resolve(this.event.channel);
this.updateChannel();
}
onCmdIlink(msg, link)
{
if (!this.bot.isMod(msg.member)) {
return;
}
this.event.link = link;
console.log("New impromptu event link: " + this.event.link);
msg.channel.send(":link: The livestream can be found here: <" + this.event.link + ">");
msg.delete();
this.updateChannel();
}
onCmdInow(msg)
{
if (!this.bot.isMod(msg.member)) {
return;
}
if (arguments.length == 1) {
return;
}
this.current = Array.from(arguments).slice(1).join(" ").trim();
this.ended = false;
console.log("Starting now: " + this.current);
msg.channel.send(":red_circle: STARTING NOW: **" + this.current + "**");
msg.delete();
this.updateChannel();
}
onCmdIoffline(msg)
{
if (!this.bot.isMod(msg.member)) {
return;
}
this.current = "";
this.ended = false;
console.log("Stream is offline!");
msg.channel.send(":no_entry_sign: Stream is temporarily offline.");
msg.delete();
this.updateChannel();
}
onCmdIend(msg)
{
if (!this.bot.isMod(msg.member)) {
return;
}
this.current = "";
this.ended = true;
console.log("End of event!");
msg.channel.send(":tada: Thank you for tuning in.");
msg.delete();
this.updateChannel();
}
onCmdCurrent(msg) { this.onCmdNp(msg); }
onCmdNow(msg) { this.onCmdNp(msg); }
onCmdNp(msg)
{
if (this.current != "") {
msg.channel.send(":red_circle: Now playing: **" + this.current + "**");
} else {
msg.channel.send(":robot: There's currently no set playing.");
}
}
onCmdNext(msg)
{
msg.channel.send(":robot: This is an event without a timetable.");
}
onCmdLink(msg)
{
msg.channel.send(":link: The livestream can be found here: <" + this.event.link + ">");
}
updateChannel()
{
var line = "";
if (this.ended) {
line = ":tada: Thank you for tuning in.";
} else {
if (this.current == "") {
line = ":no_entry_sign: Not currently live.";
} else {
line = "__" + this.current + "__";
}
if (this.event.link !== undefined) {
line += " :link: " + this.event.link;
}
}
if (this.event.emoji) {
line = this.event.emoji + " " + line;
}
this.channel.setTopic(line);
}
}
module.exports = EventQuickModule;

70
modules/filter.js Normal file
View file

@ -0,0 +1,70 @@
const discord = require("discord.js");
const RedditRadio = require("../RedditRadio");
class FilterModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
/** @type {RedditRadio} */
this.bot = bot;
if (this.config.channel) {
if (!this.config.channels) {
this.config.channels = [];
}
this.config.channels.push(this.config.channel);
}
}
/**
* @param {discord.Message} msg
* @param {Boolean} edited
*/
onMessage(msg, edited)
{
if (this.bot.isMod(msg.member)) {
return;
}
var shouldDelete = false;
// Only filter if we're in the right channel
if (this.config.channels) {
var isInChannel = false;
for (const channelID of this.config.channels) {
if (msg.channel.id == channelID) {
isInChannel = true;
break;
}
}
if (!isInChannel) {
return;
}
}
// Check for bad words (case insensitive)
if (this.config.words && msg.content.toLowerCase().match(this.config.words)) {
shouldDelete = true;
}
// Check for bad tokens (case sensitive)
if (this.config.tokens && msg.content.match(this.config.tokens)) {
shouldDelete = true;
}
if (shouldDelete) {
var usermessage = this.config.usermessage || "Your recent message has been automatically deleted. Please take another look at the rules in #info. We automatically delete messages for things like piracy and advertising.";
msg.delete();
this.bot.addLogMessage("Deleted unwanted message from " + msg.author.toString() + " in " + msg.channel.toString() + ": `" + msg.content.replace('`', '\\`') + "`");
msg.author.send(usermessage).catch(console.error);
}
}
}
module.exports = FilterModule;

34
modules/filteremotes.js Normal file
View file

@ -0,0 +1,34 @@
const discord = require("discord.js");
const RedditRadio = require("../RedditRadio");
class FilterEmotesModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
/** @type {RedditRadio} */
this.bot = bot;
}
/**
* @param {discord.Message} msg
* @param {Boolean} edited
*/
onMessage(msg, edited)
{
var limit = this.config.limit || 14;
var emotes = msg.content.toLowerCase().match(/(<a?:[^:]+:[0-9]+>|\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g);
if (emotes && emotes.length > limit) {
msg.delete();
this.bot.addLogMessage("Deleted message from " + msg.member.toString() + " in " + msg.channel.toString() + " that contained " + emotes.length + " emotes");
msg.author.send("You posted too many emojis. Calm down a little bit!").catch(console.error);
}
}
}
module.exports = FilterEmotesModule;

45
modules/filterinvite.js Normal file
View file

@ -0,0 +1,45 @@
const discord = require("discord.js");
const RedditRadio = require("../RedditRadio");
class FilterInviteModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
/** @type {RedditRadio} */
this.bot = bot;
}
/**
* @param {discord.Message} msg
* @param {Boolean} edited
*/
onMessage(msg, edited)
{
if (this.bot.isMod(msg.member)) {
return;
}
var inviteLinks = msg.content.matchAll(/(discord\.gg|discord\.com\/invite|discordapp\.com\/invite)\/([A-Za-z0-9]+)/gi);
for (const link of inviteLinks) {
var inviteCode = link[2];
var isWhitelisted = false;
if (this.config.whitelist) {
isWhitelisted = (this.config.whitelist.indexOf(inviteCode) != -1);
}
if (!isWhitelisted) {
msg.delete();
this.bot.addLogMessage("Deleted Discord invite link from " + msg.author.toString() + " in " + msg.channel.toString() + ": `" + msg.content.replace('`', '\\`') + "`");
msg.author.send("Your recent message has been automatically deleted. Please do not post Discord invite links without prior permission from a moderator or admin.").catch(console.error);
}
}
}
}
module.exports = FilterInviteModule;

92
modules/filterlink.js Normal file
View file

@ -0,0 +1,92 @@
const discord = require("discord.js");
const RedditRadio = require("../RedditRadio");
var moment = require("moment");
class FilterLinkModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
/** @type {RedditRadio} */
this.bot = bot;
/** @type {String[]} */
this.permitted = [];
}
isPermitted(member)
{
var delay = this.config.minutes || 60;
var minutes = moment().diff(member.joinedTimestamp, "minutes");
if (minutes >= delay) {
return true;
}
// Check the list of permitted users with .permit
var permittedIndex = this.permitted.indexOf(member.id);
if (permittedIndex != -1) {
if (minutes >= delay) {
this.permitted.splice(permittedIndex, 1);
}
return true;
}
return false;
}
/**
* @param {discord.Message} msg
* @param {Boolean} edited
*/
onMessage(msg, edited)
{
if (msg.content.match(/https?:\/\//i) || msg.content.match(/\.[a-z]{2,3}\//i) || msg.content.match(/(bit.ly|shorturl.at|tiny.cc)/i)) {
msg.guild.members.fetch(msg.author).then((member) => {
if (this.isPermitted(member)) {
return;
}
msg.delete();
msg.author.send("Your recent message has been automatically deleted. Brand new members can't post links for a short while, to combat spam. Check #info for more information about the rules. If you think this message is in error, please DM one of the mods.").catch(console.error);
var minutes = moment().diff(member.joinedTimestamp, "minutes");
this.bot.addLogMessage("Deleted link from " + member.toString() + " in " + msg.channel.toString() + " who joined " + minutes + " minutes ago. Deleted message:\n```" + msg.content + "```");
});
}
}
/**
* @param {discord.Message} msg
* @param {String} user
*/
onCmdPermit(msg, user)
{
if (!this.bot.isMod(msg.member)) {
return;
}
var mentions = "";
var num = 0;
msg.mentions.members.each(member => {
if (this.isPermitted(member)) {
return;
}
this.permitted.push(member.id);
mentions += member.toString() + " ";
num++;
});
if (num > 0) {
msg.channel.send(mentions + "A moderator has permitted you to post links!");
this.bot.addLogMessage(msg.member.toString() + " has permitted " + mentions + "to post links");
}
}
}
module.exports = FilterLinkModule;

55
modules/fun.js Normal file
View file

@ -0,0 +1,55 @@
const discord = require("discord.js");
const RedditRadio = require("../RedditRadio");
class FunModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
/** @type {RedditRadio} */
this.bot = bot;
}
/**
* @param {discord.Message} msg
* @param {Boolean} edited
*/
onMessage(msg, edited)
{
if (msg.content.toLowerCase() == "good bot") {
msg.channel.send(msg.member.toString() + " Thanks");
return;
}
if (msg.content.toLowerCase() == "bad bot") {
msg.channel.send(msg.member.toString() + " I'm sorry :sob: If I did something wrong, you can report a bug! <https://github.com/codecat/reddit-radio/issues>");
return;
}
if (msg.content.toLowerCase() == "kut bot") {
msg.channel.send(msg.member.toString() + " nou sorry hoor");
return;
}
if (msg.content.toLowerCase().indexOf("am i the only one") != -1 && msg.member !== null) {
msg.channel.send(msg.member.toString() + " Probably not.");
return;
}
if (msg.content.toLowerCase().indexOf(".shrug") != -1) {
msg.channel.send("\xaf\\\\\\_<:headykappa:330110432209797123>\\_/\xaf");
return;
}
if (msg.content.toLowerCase() == "<@!327816989114630145> <a:catjam150:760555281414881380>") {
msg.channel.send("<a:catjam150:760555281414881380>");
return;
}
}
}
module.exports = FunModule;

29
modules/joinreact.js Normal file
View file

@ -0,0 +1,29 @@
const discord = require("discord.js");
class JoinReactModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
this.bot = bot;
}
/**
* @param {discord.Message} msg
* @param {Boolean} edited
*/
onMessage(msg, edited)
{
if (msg.system && msg.type == "GUILD_MEMBER_JOIN") {
setTimeout(() => {
try {
msg.react(this.config.emoji || "👋");
} catch (err) { console.error(err); }
}, 2000);
}
}
}
module.exports = JoinReactModule;

29
modules/poll.js Normal file
View file

@ -0,0 +1,29 @@
const discord = require("discord.js");
const RedditRadio = require("../RedditRadio");
class PollModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
/** @type {RedditRadio} */
this.bot = bot;
}
/**
* @param {discord.Message} msg
*/
onCmdPoll(msg)
{
(async () => {
await msg.react("👍");
await msg.react("👎");
})();
}
}
module.exports = PollModule;

212
modules/producing.js Normal file
View file

@ -0,0 +1,212 @@
var colors = require("colors");
var moment = require("moment");
const ffmpeg = require('fluent-ffmpeg');
class ProducingModule
{
constructor(config, client, bot)
{
this.config = config;
this.client = client;
this.bot = bot;
this.client.on("messageReactionAdd", (r, user) => { this.onMessageReactionAdd(r, user); });
this.client.on("messageReactionRemove", (r, user) => { this.onMessageReactionRemove(r, user); });
if (!this.bot.mongodb) {
console.error("The producing module requires MongoDB to be connected to a database!");
return;
}
this.collUsers = this.bot.mongodb.collection("users");
this.collFiles = this.bot.mongodb.collection("files");
this.collFilesFeedback = this.bot.mongodb.collection("files_feedback");
}
async getOrCreateUser(id)
{
var user = await this.collUsers.findOne({ id: id });
if (!user) {
user = {
id: id,
files_uploaded: 0,
feedback_given: 0
};
await this.collUsers.insertOne(user);
}
return user;
}
async getNumberOfFeedbackGiven(userId)
{
var result = await this.collFilesFeedback.aggregate([
{ $match: { user: userId } },
{ $group: { _id: "$msg", count: { $sum: 1 } } },
{ $count: "count" }
]).next();
if (!result) {
return 0;
}
return result.count;
}
onMessageReactionAdd(r, user)
{
if (user == this.client.user) {
return;
}
var msg = r.message;
if (msg.channel.id != this.config.channel) {
return;
}
if (user == msg.author) {
return;
}
if (this.config.reactions.indexOf(r.emoji.name) == -1) {
return;
}
this.collFilesFeedback.insertOne({
time: new Date(),
msg: msg.id,
msg_user: msg.author.id,
user: user.id,
emoji: r.emoji.name
});
}
onMessageReactionRemove(r, user)
{
if (user == this.client.user) {
return;
}
var msg = r.message;
if (msg.channel.id != this.config.channel) {
return;
}
if (user == msg.author) {
return;
}
if (this.config.reactions.indexOf(r.emoji.name) == -1) {
return;
}
this.collFilesFeedback.deleteOne({
msg: msg.id,
user: user.id,
emoji: r.emoji.name
});
}
onMessage(msg, edited)
{
if (msg.channel.id != this.config.channel) {
return false;
}
if (edited) {
return false;
}
(async () => {
var user = await this.getOrCreateUser(msg.author.id);
var filenames = "";
var numFiles = 0;
msg.attachments.each(async a => {
if (!a.name.match(/.*\.(wav|mp3|ogg|flac)/)) {
return;
}
filenames += a.name + " ";
numFiles++;
var logUsername = msg.author.username + '#' + msg.author.discriminator;
console.log(logUsername + " uploaded " + a.name.red.underline);
this.collUsers.updateOne({ id: user.id }, {
$inc: { files_uploaded: 1 }
});
/*
statsmessage = false
feedbackmessage = false
spectrumpic = false
*/
if (this.config.spectrumpic) {
new Promise((resolve, reject) => {
let path = '/tmp/waveform-' + msg.id + '.png';
let cmd = ffmpeg(a.url);
cmd.complexFilter([
'[0:a] showspectrumpic=s=400x70:color=nebulae:legend=false [tmp1]',
//'[0:a] showwavespic=s=400x70:colors=0xFFFFFFFF:filter=peak [tmp2]',
//'[tmp1][tmp2] overlay=y=0:format=rgb:alpha=premultiplied [tmp3]',
'[tmp1] drawbox=0:0:400:70:black',
]);
cmd.frames(1);
cmd.on('error', err => {
reject(err);
});
cmd.on('end', () => {
msg.channel.send({
files: [{
attachment: path,
name: 'waveform.png',
}],
}).then(resolve).catch(reject);
});
cmd.save(path);
}).catch(err => {
console.error('ffmpeg waveform failed!', err);
});
}
for (var i = 0; i < this.config.reactions.length; i++) {
await msg.react(this.config.reactions[i]);
}
});
if (numFiles > 0) {
var numFeedbackGiven = await this.getNumberOfFeedbackGiven(user.id);
if (this.config.statsmessage) {
msg.channel.send("**Give " + msg.member.displayName + " your feedback!** :outbox_tray: " + (user.files_uploaded + 1) + " / :bulb: " + numFeedbackGiven);
}
if (this.config.feedbackmessage) {
if (numFeedbackGiven < user.files_uploaded) {
msg.channel.send(msg.member.toString() + " Remember to give others feedback, too! :ok_hand:");
}
}
}
})();
return false;
}
async onCmdStats(msg)
{
if (msg.channel.id != this.config.channel) {
return;
}
var user = await this.getOrCreateUser(msg.author.id);
var numFeedbackReceived = await this.collFilesFeedback.countDocuments({ msg_user: user.id });
var numFeedbackGiven = await this.getNumberOfFeedbackGiven(user.id);
msg.channel.send(":bar_chart: " + msg.member.toString() + ", you have uploaded **" + (user.files_uploaded || 0) + "** files, given **" + numFeedbackGiven + "** feedback reactions, and received **" + numFeedbackReceived + "**.");
}
}
module.exports = ProducingModule;

65
modules/qdance.js Normal file
View file

@ -0,0 +1,65 @@
var discord = require("discord.js");
var http = require("https");
class QdanceModule
{
get(dir)
{
return new Promise((resolve, reject) => {
http.get("https://feed.q-dance.com/onair", function(res) {
var data = "";
res.setEncoding("utf8");
res.on("data", function(chunk) { data += chunk; });
res.on("end", function() {
var obj = JSON.parse(data);
if (dir == -1) {
resolve(obj.TrackData.PreviousPlaying);
} else if (dir == 0) {
resolve(obj.TrackData.NowPlaying);
} else if (dir == 1) {
resolve(obj.TrackData.NextPlaying);
}
reject('Unknown track direction!');
});
});
});
}
makeEmbed(track, title)
{
var embed = new discord.MessageEmbed({
title: title,
description: track.Artist + " - " + track.Title,
hexColor: "#D26F1C"
});
embed.setAuthor("Q-dance Radio", "https://4o4.nl/20170908JHxVy.png");
embed.setThumbnail(track.CoverImage);
return embed;
}
async onCmdQdnp(msg)
{
var track = await this.get(0);
msg.channel.send({
embeds: [ this.makeEmbed(track, "Q-dance Radio is now playing:") ],
});
}
async onCmdQdnext(msg)
{
var track = await this.get(1);
msg.channel.send({
embeds: [ this.makeEmbed(track, "Next track on Q-dance Radio:") ],
});
}
async onCmdQdprev(msg)
{
var track = await this.get(-1);
msg.channel.send({
embeds: [ this.makeEmbed(track, "Previous track on Q-dance Radio:") ],
});
}
}
module.exports = QdanceModule;

27
modules/sus.js Normal file
View file

@ -0,0 +1,27 @@
const discord = require("discord.js");
var moment = require("moment");
class SusModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
this.bot = bot;
}
/**
* @param {discord.GuildMember} member
*/
onMemberJoin(member)
{
if (moment().diff(member.user.createdAt, "hours") < 48) {
this.bot.addLogMessage("<:skepticalpepe:743455915935662133> Brand new user account joined: " + member.user.toString()
+ " (account created <t:" + moment(member.user.createdAt).unix() + ":R>)");
}
}
}
module.exports = SusModule;

60
modules/tools.js Normal file
View file

@ -0,0 +1,60 @@
var moment = require("moment");
class ToolsModule
{
constructor(config, client, bot)
{
this.client = client;
this.bot = bot;
}
onCmdJoinTime(msg) { this.onCmdJoinDate(msg); }
onCmdJoinDate(msg)
{
msg.guild.members.fetch(msg.author).then((member) => {
var joinedAt = moment(member.joinedTimestamp);
msg.reply("you joined this server **<t:" + joinedAt.unix() + ":R>** - <t:" + joinedAt.unix() + ":F>");
});
}
onCmdSlow(msg, seconds)
{
if (!this.bot.isMod(msg.member)) {
return;
}
msg.channel.setRateLimitPerUser(parseInt(seconds));
msg.delete();
}
async onCmdUserInfo(msg, user)
{
if (!this.bot.isMod(msg.member)) {
return;
}
try {
var member = await msg.guild.members.fetch(user);
} catch {
var fetcheduser = await this.client.users.fetch(user);
var createdAt = moment(fetcheduser.createdTimestamp);
msg.channel.send(
"**Info for non-member " + fetcheduser.tag + "**:\n" +
":alarm_clock: Account age: **<t:" + createdAt.unix() + ":R>** - <t:" + createdAt.unix() + ":F>"
);
return;
}
var joinedAt = moment(member.joinedTimestamp);
var createdAt = moment(member.user.createdTimestamp);
msg.channel.send(
"**Info for member " + member.toString() + "**:\n" +
":alarm_clock: Join time: **<t:" + joinedAt.unix() + ":R>** - <t:" + joinedAt.unix() + ":F>\n" +
":alarm_clock: Account age: **<t:" + createdAt.unix() + ":R>** - <t:" + createdAt.unix() + ":F>"
);
}
}
module.exports = ToolsModule;

40
modules/welcome.js Normal file
View file

@ -0,0 +1,40 @@
const discord = require("discord.js");
class WelcomeModule
{
constructor(config, client, bot)
{
this.config = config;
/** @type {discord.Client} */
this.client = client;
this.bot = bot;
}
/**
* @param {discord.GuildMember} member
*/
async onMemberJoin(member)
{
var msg = this.config.messageprefix;
var msgIndex = Math.floor(Math.random() * this.config.messages.length);
msg += ' ' + this.config.messages[msgIndex];
msg = msg.replace('<name>', '**' + member.user.username + '**');
//msg = msg.replace('<name>', member.toString());
//^ Commented out because mentions for newly joined members are broken on the client
var channel = await this.client.channels.fetch(this.config.channel);
var message = await channel.send(msg);
/*
var message = await channel.send({
content: msg,
allowedMentions: {
users: []
}
});
*/
message.react(this.config.emoji || "👋");
}
}
module.exports = WelcomeModule;

26
package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "redditradio",
"version": "1.0.0",
"description": "discord.gg/hardstyle",
"main": "index.js",
"dependencies": {
"@discordjs/opus": "^0.3.2",
"colors": "^1.4.0",
"discord.js": "^13.1.0",
"fluent-ffmpeg": "^2.1.2",
"follow-redirects": "^1.14.4",
"moment": "^2.24.0",
"moment-timezone": "^0.5.33",
"mongodb": "^3.7.1",
"sodium": "^3.0.2",
"toml": "^3.0.0"
},
"devDependencies": {},
"scripts": {
"start": "node index.js",
"radio": "node index.js --radio",
"radios": "node index.js --radios",
"no-radio": "node index.js --no-radios"
},
"author": "Codecat"
}