From 0b8f9210d38447fc4ebe9ba08994bb89d416c51b Mon Sep 17 00:00:00 2001 From: deflax Date: Tue, 22 Mar 2022 20:25:39 -0400 Subject: [PATCH] add code from codecat --- Dockerfile | 9 + Dockerfile.base | 18 + Radio.js | 118 ++++++ RedditRadio.js | 445 ++++++++++++++++++++++ Startup.js | 101 +++++ cmdsplit.js | 54 +++ config.example.toml | 30 ++ events/Corona_2020_03_14.json | 62 ++++ events/Corona_2020_03_20.json | 27 ++ events/Corona_2020_04_12.json | 24 ++ events/Dediqated2020.json | 37 ++ events/Defqon2018.json | 117 ++++++ events/Defqon2018_Aus.json | 27 ++ events/Defqon2018_test.json | 117 ++++++ events/Defqon2019.json | 352 ++++++++++++++++++ events/Epiq2019.json | 35 ++ events/HardBass2019.json | 29 ++ events/Holland.json | 23 ++ events/Holland2019.json | 20 + events/Impaqt2019.json | 25 ++ events/Mysteryland.json | 22 ++ events/Qbase2018.json | 112 ++++++ events/Qbase2018_Single.json | 30 ++ events/Qbase2018_Two.json | 46 +++ events/Qlimax2018.json | 23 ++ events/Qlimax2019.json | 31 ++ events/Qonnect.json | 29 ++ events/Qonnect2.json | 33 ++ events/Qonnect3.json | 29 ++ events/Reverze2019.json | 26 ++ events/Reverze2020.json | 29 ++ events/TomorrowlandDominator2018.json | 47 +++ events/Tweekaz.json | 21 ++ events/WOWWOW2018.json | 28 ++ index.js | 4 + modules/autoreact.js | 29 ++ modules/event.js | 513 ++++++++++++++++++++++++++ modules/eventquick.js | 133 +++++++ modules/filter.js | 70 ++++ modules/filteremotes.js | 34 ++ modules/filterinvite.js | 45 +++ modules/filterlink.js | 92 +++++ modules/fun.js | 55 +++ modules/joinreact.js | 29 ++ modules/poll.js | 29 ++ modules/producing.js | 212 +++++++++++ modules/qdance.js | 65 ++++ modules/sus.js | 27 ++ modules/tools.js | 60 +++ modules/welcome.js | 40 ++ package.json | 26 ++ 51 files changed, 3639 insertions(+) create mode 100644 Dockerfile create mode 100644 Dockerfile.base create mode 100644 Radio.js create mode 100644 RedditRadio.js create mode 100644 Startup.js create mode 100644 cmdsplit.js create mode 100644 config.example.toml create mode 100644 events/Corona_2020_03_14.json create mode 100644 events/Corona_2020_03_20.json create mode 100644 events/Corona_2020_04_12.json create mode 100644 events/Dediqated2020.json create mode 100644 events/Defqon2018.json create mode 100644 events/Defqon2018_Aus.json create mode 100644 events/Defqon2018_test.json create mode 100644 events/Defqon2019.json create mode 100644 events/Epiq2019.json create mode 100644 events/HardBass2019.json create mode 100644 events/Holland.json create mode 100644 events/Holland2019.json create mode 100644 events/Impaqt2019.json create mode 100644 events/Mysteryland.json create mode 100644 events/Qbase2018.json create mode 100644 events/Qbase2018_Single.json create mode 100644 events/Qbase2018_Two.json create mode 100644 events/Qlimax2018.json create mode 100644 events/Qlimax2019.json create mode 100644 events/Qonnect.json create mode 100644 events/Qonnect2.json create mode 100644 events/Qonnect3.json create mode 100644 events/Reverze2019.json create mode 100644 events/Reverze2020.json create mode 100644 events/TomorrowlandDominator2018.json create mode 100644 events/Tweekaz.json create mode 100644 events/WOWWOW2018.json create mode 100644 index.js create mode 100644 modules/autoreact.js create mode 100644 modules/event.js create mode 100644 modules/eventquick.js create mode 100644 modules/filter.js create mode 100644 modules/filteremotes.js create mode 100644 modules/filterinvite.js create mode 100644 modules/filterlink.js create mode 100644 modules/fun.js create mode 100644 modules/joinreact.js create mode 100644 modules/poll.js create mode 100644 modules/producing.js create mode 100644 modules/qdance.js create mode 100644 modules/sus.js create mode 100644 modules/tools.js create mode 100644 modules/welcome.js create mode 100644 package.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..731ef49 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM codecatt/reddit-radio:base + +ENV CONFIG_FILE="./config/config.toml" + +WORKDIR /app + +COPY . /app + +CMD ["node", "index.js"] diff --git a/Dockerfile.base b/Dockerfile.base new file mode 100644 index 0000000..fa55f3e --- /dev/null +++ b/Dockerfile.base @@ -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 diff --git a/Radio.js b/Radio.js new file mode 100644 index 0000000..daa7627 --- /dev/null +++ b/Radio.js @@ -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; diff --git a/RedditRadio.js b/RedditRadio.js new file mode 100644 index 0000000..33a650c --- /dev/null +++ b/RedditRadio.js @@ -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") + "** ()"; + msg.channel.send(text); + } +} + +module.exports = RedditRadio; diff --git a/Startup.js b/Startup.js new file mode 100644 index 0000000..e2f0613 --- /dev/null +++ b/Startup.js @@ -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; diff --git a/cmdsplit.js b/cmdsplit.js new file mode 100644 index 0000000..f5017fd --- /dev/null +++ b/cmdsplit.js @@ -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; +}; diff --git a/config.example.toml b/config.example.toml new file mode 100644 index 0000000..5074960 --- /dev/null +++ b/config.example.toml @@ -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 diff --git a/events/Corona_2020_03_14.json b/events/Corona_2020_03_14.json new file mode 100644 index 0000000..866b5cb --- /dev/null +++ b/events/Corona_2020_03_14.json @@ -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: ****", + "^\\.(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: " + } + }, + { + "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: ****", + "^\\.(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: " + } + }, + { + "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: ****", + "^\\.(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: " + } + } +] diff --git a/events/Corona_2020_03_20.json b/events/Corona_2020_03_20.json new file mode 100644 index 0000000..07565c2 --- /dev/null +++ b/events/Corona_2020_03_20.json @@ -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: ****", + "^\\.(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: " + } + } +] diff --git a/events/Corona_2020_04_12.json b/events/Corona_2020_04_12.json new file mode 100644 index 0000000..1037f82 --- /dev/null +++ b/events/Corona_2020_04_12.json @@ -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: ****", + "^\\.(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: ", + "^\\.(ultrawide)$": "For the ultrawide viewers, this userscript is nice: " + } + } +] diff --git a/events/Dediqated2020.json b/events/Dediqated2020.json new file mode 100644 index 0000000..7b2bd29 --- /dev/null +++ b/events/Dediqated2020.json @@ -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: ****", + "^\\.(mc|host)$": ":microphone: The MCs are **Villain**, **DV8**, and **Ruffian**." + } + } +] diff --git a/events/Defqon2018.json b/events/Defqon2018.json new file mode 100644 index 0000000..6188623 --- /dev/null +++ b/events/Defqon2018.json @@ -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" ] + ] + } +] diff --git a/events/Defqon2018_Aus.json b/events/Defqon2018_Aus.json new file mode 100644 index 0000000..d5b4fb8 --- /dev/null +++ b/events/Defqon2018_Aus.json @@ -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" ] + ] + } +] diff --git a/events/Defqon2018_test.json b/events/Defqon2018_test.json new file mode 100644 index 0000000..c166041 --- /dev/null +++ b/events/Defqon2018_test.json @@ -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" ] + ] + } +] diff --git a/events/Defqon2019.json b/events/Defqon2019.json new file mode 100644 index 0000000..033d6e8 --- /dev/null +++ b/events/Defqon2019.json @@ -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": [ + ] + } +] diff --git a/events/Epiq2019.json b/events/Epiq2019.json new file mode 100644 index 0000000..377f02f --- /dev/null +++ b/events/Epiq2019.json @@ -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: ****", + "^\\.(mc|host)$": ":microphone: The top 100 is hosted by **Tellem**. The MC during Epiq is **Villan**." + } + } +] diff --git a/events/HardBass2019.json b/events/HardBass2019.json new file mode 100644 index 0000000..99840da --- /dev/null +++ b/events/HardBass2019.json @@ -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: ", + "The timer is counting down until the end of Hard Bass. (hh:mm:ss.mm)" + ] + } +] diff --git a/events/Holland.json b/events/Holland.json new file mode 100644 index 0000000..3734cdb --- /dev/null +++ b/events/Holland.json @@ -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" ] + ] + } +] diff --git a/events/Holland2019.json b/events/Holland2019.json new file mode 100644 index 0000000..474f8cb --- /dev/null +++ b/events/Holland2019.json @@ -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" ] + ] + } +] diff --git a/events/Impaqt2019.json b/events/Impaqt2019.json new file mode 100644 index 0000000..564c1f9 --- /dev/null +++ b/events/Impaqt2019.json @@ -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" ] + ] + } +] diff --git a/events/Mysteryland.json b/events/Mysteryland.json new file mode 100644 index 0000000..486cc28 --- /dev/null +++ b/events/Mysteryland.json @@ -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" ] + ] + } +] diff --git a/events/Qbase2018.json b/events/Qbase2018.json new file mode 100644 index 0000000..a76fa45 --- /dev/null +++ b/events/Qbase2018.json @@ -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" ] + ] + } +] diff --git a/events/Qbase2018_Single.json b/events/Qbase2018_Single.json new file mode 100644 index 0000000..99601a0 --- /dev/null +++ b/events/Qbase2018_Single.json @@ -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" ] + ] + } +] diff --git a/events/Qbase2018_Two.json b/events/Qbase2018_Two.json new file mode 100644 index 0000000..fef0ccc --- /dev/null +++ b/events/Qbase2018_Two.json @@ -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" ] + ] + } +] diff --git a/events/Qlimax2018.json b/events/Qlimax2018.json new file mode 100644 index 0000000..a81ea1b --- /dev/null +++ b/events/Qlimax2018.json @@ -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" ] + ] + } +] diff --git a/events/Qlimax2019.json b/events/Qlimax2019.json new file mode 100644 index 0000000..9baaceb --- /dev/null +++ b/events/Qlimax2019.json @@ -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: **** (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." + } + } +] diff --git a/events/Qonnect.json b/events/Qonnect.json new file mode 100644 index 0000000..b76dfad --- /dev/null +++ b/events/Qonnect.json @@ -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: ****", + "^\\.(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**." + } + } +] diff --git a/events/Qonnect2.json b/events/Qonnect2.json new file mode 100644 index 0000000..799606e --- /dev/null +++ b/events/Qonnect2.json @@ -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: ****", + "^\\.(mc|host)$": ":microphone: The hosts are Tellem & Livid." + } + } +] diff --git a/events/Qonnect3.json b/events/Qonnect3.json new file mode 100644 index 0000000..ffb5918 --- /dev/null +++ b/events/Qonnect3.json @@ -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: ****", + "^\\.(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\");```" + } + } +] diff --git a/events/Reverze2019.json b/events/Reverze2019.json new file mode 100644 index 0000000..5b15975 --- /dev/null +++ b/events/Reverze2019.json @@ -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." + ] + } +] diff --git a/events/Reverze2020.json b/events/Reverze2020.json new file mode 100644 index 0000000..fd41dfe --- /dev/null +++ b/events/Reverze2020.json @@ -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: ****", + "^\\.(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." + } + } +] diff --git a/events/TomorrowlandDominator2018.json b/events/TomorrowlandDominator2018.json new file mode 100644 index 0000000..9d2a854 --- /dev/null +++ b/events/TomorrowlandDominator2018.json @@ -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" ] + ] + } +] diff --git a/events/Tweekaz.json b/events/Tweekaz.json new file mode 100644 index 0000000..6328d9f --- /dev/null +++ b/events/Tweekaz.json @@ -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" ] + ] + } +] diff --git a/events/WOWWOW2018.json b/events/WOWWOW2018.json new file mode 100644 index 0000000..d9a9139 --- /dev/null +++ b/events/WOWWOW2018.json @@ -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" ] + ] + } +] diff --git a/index.js b/index.js new file mode 100644 index 0000000..ecfd9ed --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +var Startup = require("./Startup"); + +let startup = new Startup(); +startup.run(); diff --git a/modules/autoreact.js b/modules/autoreact.js new file mode 100644 index 0000000..e4431b7 --- /dev/null +++ b/modules/autoreact.js @@ -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; diff --git a/modules/event.js b/modules/event.js new file mode 100644 index 0000000..e68070c --- /dev/null +++ b/modules/event.js @@ -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 ! (" + current.who + ")"); + } else { + msg.channel.send(":red_circle: Now playing: **" + current.name + "**, started !"); + } + } 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 += " ()"; + + 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 += "- (), the stream will be offline :no_entry_sign:\n"; + } else { + if (set.who) { + ret += "- (): **" + set.name + "** (" + set.who + ")\n"; + } else { + ret += "- (): **" + 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 += " ()"; + + 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 \n"; + } else { + ret += res.set.name + " plays on \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 ""; + } + + 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 + "__ "; + } else { + line += " :no_entry_sign: __Not currently live__."; + } + + if (next !== null) { + line += " :arrow_forward: Next: __" + next.name + "__ "; + } 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; diff --git a/modules/eventquick.js b/modules/eventquick.js new file mode 100644 index 0000000..10ca353 --- /dev/null +++ b/modules/eventquick.js @@ -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; diff --git a/modules/filter.js b/modules/filter.js new file mode 100644 index 0000000..44f7ae4 --- /dev/null +++ b/modules/filter.js @@ -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; diff --git a/modules/filteremotes.js b/modules/filteremotes.js new file mode 100644 index 0000000..1628d73 --- /dev/null +++ b/modules/filteremotes.js @@ -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(/(|\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; diff --git a/modules/filterinvite.js b/modules/filterinvite.js new file mode 100644 index 0000000..f14c019 --- /dev/null +++ b/modules/filterinvite.js @@ -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; diff --git a/modules/filterlink.js b/modules/filterlink.js new file mode 100644 index 0000000..8f9ec14 --- /dev/null +++ b/modules/filterlink.js @@ -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; diff --git a/modules/fun.js b/modules/fun.js new file mode 100644 index 0000000..5f0471a --- /dev/null +++ b/modules/fun.js @@ -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! "); + 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> ") { + msg.channel.send(""); + return; + } + } +} + +module.exports = FunModule; diff --git a/modules/joinreact.js b/modules/joinreact.js new file mode 100644 index 0000000..105d3da --- /dev/null +++ b/modules/joinreact.js @@ -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; diff --git a/modules/poll.js b/modules/poll.js new file mode 100644 index 0000000..9ff4a1b --- /dev/null +++ b/modules/poll.js @@ -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; diff --git a/modules/producing.js b/modules/producing.js new file mode 100644 index 0000000..9927fef --- /dev/null +++ b/modules/producing.js @@ -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; diff --git a/modules/qdance.js b/modules/qdance.js new file mode 100644 index 0000000..4462c65 --- /dev/null +++ b/modules/qdance.js @@ -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; diff --git a/modules/sus.js b/modules/sus.js new file mode 100644 index 0000000..53b49b7 --- /dev/null +++ b/modules/sus.js @@ -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 )"); + } + } +} + +module.exports = SusModule; diff --git a/modules/tools.js b/modules/tools.js new file mode 100644 index 0000000..84f6297 --- /dev/null +++ b/modules/tools.js @@ -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 **** - "); + }); + } + + 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: **** - " + ); + 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: **** - \n" + + ":alarm_clock: Account age: **** - " + ); + } +} + +module.exports = ToolsModule; diff --git a/modules/welcome.js b/modules/welcome.js new file mode 100644 index 0000000..d897b08 --- /dev/null +++ b/modules/welcome.js @@ -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('', '**' + member.user.username + '**'); + //msg = msg.replace('', 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; diff --git a/package.json b/package.json new file mode 100644 index 0000000..fa9f3a7 --- /dev/null +++ b/package.json @@ -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" +}