浏览代码

Merge branch 'february_release' of https://github.com/Musare/Musare into february_release

Akira Laine 9 年之前
父节点
当前提交
a8f543c62d
共有 6 个文件被更改,包括 897 次插入59 次删除
  1. 1 0
      app/.meteor/packages
  2. 3 0
      app/.meteor/versions
  3. 377 0
      app/client/scripts/events.js
  4. 0 0
      app/database/collections.js
  5. 495 0
      app/database/schemas.js
  6. 21 59
      app/server/server.js

+ 1 - 0
app/.meteor/packages

@@ -30,4 +30,5 @@ momentjs:moment
 emojione:emojione
 utilities:avatar
 matb33:collection-hooks
+aldeed:collection2
 materialize:materialize

+ 3 - 0
app/.meteor/versions

@@ -2,6 +2,8 @@ accounts-base@1.2.2
 accounts-github@1.0.6
 accounts-oauth@1.1.8
 accounts-password@1.1.4
+aldeed:collection2@2.7.0
+aldeed:simple-schema@1.3.3
 altapp:recaptcha@2.0.0
 autoupdate@1.2.4
 babel-compiler@5.8.24_1
@@ -76,6 +78,7 @@ oauth2@1.1.5
 observe-sequence@1.0.7
 ordered-dict@1.0.4
 promise@0.5.1
+raix:eventemitter@0.1.3
 random@1.0.5
 rate-limit@1.0.0
 reactive-dict@1.1.3

+ 377 - 0
app/client/scripts/events.js

@@ -1054,6 +1054,383 @@ Template.room.events({
     }
 });
 
+                    if (YTPlayer !== undefined) {
+                        YTPlayer.setVolume(val.value);
+                        localStorage.setItem("volume", val.value);
+                    } else if (SCPlayer !== undefined) {
+                        //SCPlayer
+                        var volume = val.value / 100;
+                        SCPlayer.setVolume(volume);
+                        localStorage.setItem("volume", val.value);
+                    }
+                });
+            }
+        }
+        makeSlider();
+    });
+});
+
+Template.room.helpers({
+    singleVideo: function() {
+        return true;
+    },
+    chat: function() {
+        Meteor.setTimeout(function() {
+            var elem = document.getElementById('chat');
+            if (elem !== undefined && elem !== null) {
+                elem.scrollTop = elem.scrollHeight;
+            }
+        }, 100);
+        return Chat.find({type: Session.get("type")}, {sort: {time: -1}, limit: 50 }).fetch().reverse();
+    },
+    globalChat: function() {
+        Meteor.setTimeout(function() {
+            var elem = document.getElementById('global-chat');
+            if (elem !== undefined && elem !== null) {
+                elem.scrollTop = elem.scrollHeight;
+            }
+        }, 100);
+        return Chat.find({type: "global"}, {sort: {time: -1}, limit: 50 }).fetch().reverse();
+    },
+    likes: function() {
+        var playlist = Playlists.findOne({type: Session.get("type")});
+        var likes = 0;
+        playlist.songs.forEach(function(song) {
+            if (Session.get("currentSong") && song.mid === Session.get("currentSong").mid) {
+                likes = song.likes;
+                return;
+            }
+        });
+        return likes;
+    },
+    dislikes: function() {
+        var playlist = Playlists.findOne({type: Session.get("type")});
+        var dislikes = 0;
+        playlist.songs.forEach(function(song) {
+            if (Session.get("currentSong") && song.mid === Session.get("currentSong").mid) {
+                dislikes = song.dislikes;
+                return;
+            }
+        });
+        return dislikes;
+    },
+    liked: function() {
+        if (Meteor.userId()) {
+            var currentSong = Session.get("currentSong");
+            if (currentSong && Meteor.user().profile.liked.indexOf(currentSong.mid) !== -1) {
+                return "active";
+            } else {
+                return "";
+            }
+        } else {
+            "";
+        }
+    },
+    disliked: function() {
+        if (Meteor.userId()) {
+            var currentSong = Session.get("currentSong");
+            if (currentSong && Meteor.user().profile.disliked.indexOf(currentSong.mid) !== -1) {
+                return "active";
+            } else {
+                return "";
+            }
+        } else {
+            "";
+        }
+    },
+    type: function() {
+        var parts = location.href.split('/');
+        var id = parts.pop().toLowerCase();
+        return Rooms.findOne({type: id}).display;
+    },
+    users: function() {
+        var parts = location.href.split('/');
+        var id = parts.pop().toLowerCase();
+        return Rooms.findOne({type: id}).users;
+    },
+    title: function(){
+        return Session.get("title");
+    },
+    artist: function(){
+        return Session.get("artist");
+    },
+    loaded: function() {
+        return Session.get("loaded");
+    },
+    paused: function() {
+        return Session.get("state") === "paused";
+    },
+    private: function() {
+        return Rooms.findOne({type: Session.get("type")}).private === true;
+    },
+    report: function() {
+        return Session.get("reportObj");
+    },
+    reportSong: function() {
+        return Session.get("reportSong");
+    },
+    reportTitle: function() {
+        return Session.get("reportTitle");
+    },
+    reportAuthor: function() {
+        return Session.get("reportAuthor");
+    },
+    reportDuration: function() {
+        return Session.get("reportDuration");
+    },
+    reportAudio: function() {
+        return Session.get("reportAudio");
+    },
+    reportAlbumart: function() {
+        return Session.get("reportAlbumart");
+    },
+    reportOther: function() {
+        return Session.get("reportOther");
+    },
+    currentSong: function() {
+        return Session.get("currentSong");
+    },
+    previousSong: function() {
+        return Session.get("previousSong");
+    },
+    currentSongR: function() {
+        return Session.get("currentSongR");
+    },
+    previousSongR: function() {
+        return Session.get("previousSongR");
+    },
+    reportingSong: function() {
+        if (Session.get("reportPrevious")) {
+            return Session.get("previousSongR");
+        } else {
+            return Session.get("currentSongR");
+        }
+    },
+    votes: function(){
+        return Rooms.findOne({type: Session.get("type")}).votes;
+    }
+});
+Template.room.onCreated(function () {
+    Chat.after.find(function(userId, selector) {
+        if (selector.type === "global") {
+            if (!$("#global-chat-tab").hasClass("active")) {
+                $("#global-chat-tab").addClass("unread-messages");
+            }
+        } else if(selector.type === Session.get("type")) {
+            if (!$("#chat-tab").hasClass("active")) {
+                $("#chat-tab").addClass("unread-messages");
+            }
+        }
+    });
+    Session.set("reportSong", false);
+    Session.set("reportTitle", false);
+    Session.set("reportAuthor", false);
+    Session.set("reportDuration", false);
+    Session.set("reportAudio", false);
+    Session.set("reportAlbumart", false);
+    Session.set("reportOther", false);
+    if (resizeSeekerbarInterval !== undefined) {
+        Meteor.clearInterval(resizeSeekerbarInterval);
+        resizeSeekerbarInterval = undefined;
+    }
+    YTPlayer = undefined;
+    SCPlayer = undefined;
+    Session.set("videoHidden", false);
+    var tag = document.createElement("script");
+    tag.src = "https://www.youtube.com/iframe_api";
+    var firstScriptTag = document.getElementsByTagName('script')[0];
+    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
+
+    Session.set("singleVideo", true);
+
+    var currentSong = undefined;
+    var currentSongR = undefined;
+
+    function getTimeElapsed() {
+        if (currentSong !== undefined) {
+            var room = Rooms.findOne({type: type});
+            if (room !== undefined) {
+                return Date.now() - currentSong.started - room.timePaused;
+            }
+        }
+        return 0;
+    }
+
+    function getSongInfo(songData){
+        Session.set("title", songData.title);
+        Session.set("artist", songData.artist);
+        Session.set("id", songData.id);
+        $("#song-img").attr("src", songData.img);
+        Session.set("duration", parseInt(songData.duration));
+        var d = moment.duration(parseInt(songData.duration), 'seconds');
+        $("#time-total").text(d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+        Session.set("timeFormat", d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+    }
+
+    function resizeSeekerbar() {
+        if (Session.get("state") === "playing") {
+            $("#seeker-bar").width(((getTimeElapsed() / 1000) / Session.get("duration") * 100) + "%");
+        }
+    }
+
+    function startSong() {
+        $("#time-elapsed").text("0:00");
+        $("#vote-skip").attr("disabled", false);
+        if (currentSong !== undefined) {
+            if (SCPlayer !== undefined) SCPlayer.stop();
+            if (YTPlayer !== undefined && YTPlayer.stopVideo !== undefined) YTPlayer.stopVideo();
+
+            var volume = localStorage.getItem("volume") || 20;
+
+            if (currentSong.type === "SoundCloud") {
+                if ($("#soundcloud-image").length !== 1) {
+                    //$("#media-container").append('<img alt="Not loading" src="/soundcloud-image.png" class="embed-responsive-item" id="soundcloud-image" />');
+                    $("#media-container").append('<h1 id="soundcloud-image">We have temporarily disabled the playing of SoundCloud songs. We are sorry for this inconvenience.</h1>');
+                }
+                if ($("#player").length === 1) {
+                    $("#player").hide();
+                }
+                $("#soundcloud-image").show();
+                //getSongInfo(currentSong);
+                /*SC.stream("/tracks/" + currentSong.id, function(sound){
+                    SCPlayer = sound;
+                    sound.setVolume(volume / 100);
+                    sound.play();
+                    var interval = setInterval(function() {
+                        if (sound.getState() === "playing") {
+                            sound.seek(getTimeElapsed());
+                            window.clearInterval(interval);
+                        }
+                    }, 200);
+                    Session.set("duration", parseInt(currentSong.duration));
+                    var d = moment.duration(parseInt(currentSong.duration), 'seconds');
+                    $("#time-total").text(d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+                    resizeSeekerbar();
+                });*/
+            } else {
+                if ($("#player").length !== 1) {
+                    $("#media-container").append('<div id="player" class="embed-responsive-item"></div>');
+                }
+                if ($("#soundcloud-image").length === 1) {
+                    $("#soundcloud-image").hide();
+                }
+                $("#player").show();
+                function loadVideo() {
+                    if (!Session.get("YTLoaded")) {
+                        Session.set("loadVideoTimeout", Meteor.setTimeout(function () {
+                            loadVideo();
+                        }, 500));
+                    } else {
+                        if (YTPlayer === undefined) {
+                            YTPlayer = new YT.Player("player", {
+                                height: 540,
+                                width: 960,
+                                videoId: currentSong.id,
+                                playerVars: {controls: 0, iv_load_policy: 3, rel: 0, showinfo: 0},
+                                events: {
+                                    'onReady': function (event) {
+                                        if (currentSong.skipDuration === undefined) {
+                                            currentSong.skipDuration = 0;
+                                        }
+                                        event.target.seekTo(Number(currentSong.skipDuration) + getTimeElapsed() / 1000);
+                                        event.target.playVideo();
+                                        event.target.setVolume(volume);
+                                        resizeSeekerbar();
+                                    },
+                                    'onStateChange': function (event) {
+                                        if (Session.get("YTLoaded")) {
+                                            if (event.data == YT.PlayerState.PAUSED && Session.get("state") === "playing") {
+                                                event.target.seekTo(Number(currentSong.skipDuration) + getTimeElapsed() / 1000);
+                                                event.target.playVideo();
+                                            }
+                                            if (event.data == YT.PlayerState.PLAYING && Session.get("state") === "paused") {
+                                                event.target.seekTo(Number(currentSong.skipDuration) + getTimeElapsed() / 1000);
+                                                event.target.pauseVideo();
+                                            }
+                                        }
+                                    }
+                                }
+                            });
+                        } else {
+                            YTPlayer.loadVideoById(currentSong.id);
+                            if (currentSong.skipDuration === undefined) {
+                                currentSong.skipDuration = 0;
+                            }
+                            YTPlayer.seekTo(Number(currentSong.skipDuration) + getTimeElapsed() / 1000);
+                        }
+                        Session.set("pauseVideo", false);
+                        getSongInfo(currentSong);
+                    }
+                }
+                loadVideo();
+            }
+        }
+    }
+
+    Session.set("loaded", false);
+    Meteor.subscribe("rooms", function() {
+        var parts = location.href.split('/');
+        var id = parts.pop();
+        var type = id.toLowerCase();
+        Session.set("type", type);
+        if (Rooms.find({type: type}).count() !== 1) {
+            window.location = "/";
+        } else {
+            StationSubscription = Meteor.subscribe(type);
+            Session.set("loaded", true);
+            minterval = Meteor.setInterval(function () {
+                var room = Rooms.findOne({type: type});
+                if (room !== undefined) {
+                    if (room.state === "paused" || Session.get("pauseVideo")) {
+                        Session.set("state", "paused");
+                        // TODO Fix issue where sometimes nothing loads with the YT is not defined error. The error points to around this.
+                        if (YTPlayer !== undefined && YTPlayer.getPlayerState !== undefined && YTPlayer.getPlayerState() === 1) {
+                            YTPlayer.pauseVideo();
+                        } else if (SCPlayer !== undefined && SCPlayer.getState().indexOf("playing") !== -1) {
+                            SCPlayer.pause();
+                        }
+                    } else {
+                        Session.set("state", "playing");
+                        if (YTPlayer !== undefined && YTPlayer.getPlayerState !== undefined && YTPlayer.getPlayerState() !== 1) {
+                            YTPlayer.playVideo();
+                        } else if (SCPlayer !== undefined && SCPlayer.getState().indexOf("paused") !== -1) {
+                            SCPlayer.play();
+                        }
+                    }
+                }
+
+                if (room.currentSong.song !== undefined && (currentSongR === undefined || room.currentSong.started !== currentSongR.started)) {
+                    Session.set("previousSong", currentSong);
+                    currentSongR = room.currentSong;
+
+                    currentSong = room.currentSong.song;
+                    currentSong.started = room.currentSong.started;
+                    Session.set("currentSong", currentSong);
+                    Meteor.clearTimeout(Session.get("loadVideoTimeout"));
+                    startSong();
+                }
+
+                if (currentSong !== undefined) {
+                    if (room !== undefined) {
+                        var duration = (Date.now() - currentSong.started - room.timePaused) / 1000;
+                        var song_duration = currentSong.duration;
+                            if (song_duration <= duration) {
+                                Session.set("pauseVideo", true);
+                            }
+                        var d = moment.duration(duration, 'seconds');
+                        if (Session.get("state") === "playing") {
+                            $("#time-elapsed").text(d.minutes() + ":" + ("0" + d.seconds()).slice(-2));
+                        }
+                    }
+                }
+            }, 100);
+            resizeSeekerbarInterval = Meteor.setInterval(function () {
+                resizeSeekerbar();
+            }, 500);
+        }
+    });
+});
+// Settings Template
 Template.settings.events({
     "click #save-settings": function() {
         Meteor.call("updateSettings", $("#showRating").is(":checked"));

+ 0 - 0
app/collections/collections.js → app/database/collections.js


+ 495 - 0
app/database/schemas.js

@@ -0,0 +1,495 @@
+var Schemas = {};
+
+Schemas.FullSong = new SimpleSchema({
+    "id": {
+        type: String,
+        label: "Song YouTube id"
+    },
+    "mid": {
+        type: String,
+        label: "Song mid"
+    },
+    "likes": {
+        type: Number,
+        label: "Song likes",
+        defaultValue: 0
+    },
+    "dislikes": {
+        type: Number,
+        label: "Song dislikes",
+        defaultValue: 0
+    },
+    "title": {
+        type: String,
+        label: "Song title"
+    },
+    "artist": {
+        type: String,
+        label: "Song artist"
+    },
+    "img": {
+        type: String,
+        label: "Song img"
+    },
+    "type": {
+        type: String,
+        label: "Song type",
+        defaultValue: "YouTube"
+    },
+    "duration": {
+        type: Number,
+        label: "Song duration",
+        min: 0,
+        decimal: true
+    },
+    "skipDuration": {
+        type: Number,
+        label: "Song skipDuration",
+        min: 0,
+        decimal: true
+    },
+    "requestedBy": {
+        type: String,
+        label: "User ID of the person who requested the song"
+    },
+    "approvedBy": {
+        type: String,
+        label: "User ID of the person who approved the song"
+    }
+});
+
+Schemas.QueueSong = new SimpleSchema({
+    "id": {
+        type: String,
+        label: "Song YouTube id"
+    },
+    "mid": {
+        type: String,
+        label: "Song mid"
+    },
+    "likes": {
+        type: Number,
+        label: "Song likes",
+        defaultValue: 0
+    },
+    "dislikes": {
+        type: Number,
+        label: "Song dislikes",
+        defaultValue: 0
+    },
+    "title": {
+        type: String,
+        label: "Song title"
+    },
+    "artist": {
+        type: String,
+        label: "Song artist"
+    },
+    "img": {
+        type: String,
+        label: "Song img"
+    },
+    "type": {
+        type: String,
+        label: "Song type",
+        defaultValue: "YouTube"
+    },
+    "duration": {
+        type: Number,
+        label: "Song duration",
+        min: 0,
+        decimal: true
+    },
+    "skipDuration": {
+        type: Number,
+        label: "Song skipDuration",
+        min: 0,
+        decimal: true
+    },
+    "requestedBy": {
+        type: String,
+        label: "User ID of the person who requested the song"
+    }
+});
+
+Schemas.Chat = new SimpleSchema({
+    type: {
+        type: String,
+        label: "Type of the room a message was sent in",
+        regEx: /^[a-z0-9_]{1,20}$/
+    },
+    rawrank: {
+        type: String,
+        label: "Rank of the user who sent the message"
+    },
+    rank: {
+        type: String,
+        label: "Display tag of the rank of the user who sent a message",
+        optional: true
+    },
+    message: {
+        type: String,
+        label: "The message",
+        max: 300
+    },
+    username: {
+        type: String,
+        label: "Username of the user who sent the message"
+    },
+    time: {
+        type: Date,
+        label: "Date of the time the message was sent"
+    }
+});
+
+Schemas.Alert = new SimpleSchema({
+    description: {
+        type: String,
+        label: "The Alert's Description"
+    },
+    priority: {
+        type: String,
+        allowedValues: ["danger", "warning", "success", "primary"],
+        label: "The Alert's Priority"
+    },
+    active: {
+        type: Boolean,
+        label: "Whether or not the alert is active or not"
+    },
+    createdBy: {
+        type: String,
+        label: "Username of the person who created an alert"
+    }
+});
+
+Schemas.Room = new SimpleSchema({
+    display: {
+        type: String,
+        label: "Room Display Name",
+        regEx: /^[a-z0-9A-Z_\s]{1,30}$/
+    },
+    type: {
+        type: String,
+        label: "Room Type",
+        regEx: /^[a-z0-9_]{1,20}$/
+    },
+    currentSong: {
+        type: Object,
+        defaultValue: {},
+        label: "Current Song"
+    },
+    "currentSong.song": {
+        type: Schemas.FullSong,
+        label: "Current Song Object"
+    },
+    "currentSong.started": {
+        type: Number,
+        label: "Current Song Start Date"
+    },
+    timePaused: {
+        type: Number,
+        defaultValue: 0,
+        label: "Amount of time a room has been paused for"
+    },
+    users: {
+        type: Number,
+        defaultValue: 0,
+        label: "Users Online",
+        min: 0
+    },
+    state: {
+        type: String,
+        defaultValue: "paused",
+        allowedValues: ["paused", "playing"],
+        label: "Room State"
+    },
+    votes: {
+        type: Number,
+        defaultValue: 0,
+        label: "Current votes to skip current song",
+        min: 0
+    },
+    private: {
+        type: Boolean,
+        defaultValue: false,
+        label: "Room private or not"
+    }
+});
+
+Schemas.Playlist = new SimpleSchema({
+    type: {
+        type: String,
+        label: "Type of the room the playlist is for",
+        regEx: /^[a-z0-9_]{1,20}$/
+    },
+    songs: {
+        type: Array,
+        label: "All songs in that playlist"
+    },
+    "songs.$": {
+        type: Schemas.FullSong,
+        label: "Song object"
+    },
+    lastSong: {
+        type: Number,
+        label: "Index of the previous song",
+        defaultValue: 0
+    }
+});
+
+Schemas.Queue = new SimpleSchema({
+    type: {
+        type: String,
+        label: "Type of the room the playlist is for",
+        regEx: /^[a-z0-9_]{1,20}$/
+    },
+    songs: {
+        type: Array,
+        label: "All songs in that playlist"
+    },
+    "songs.$": {
+        type: Schemas.QueueSong,
+        label: "Song object"
+    }
+});
+
+Schemas.UserProfile = new SimpleSchema({
+    username: {
+        type: String,
+        label: "Username",
+        regEx: /^[a-zA-Z0-9_]+$/,
+        min: 6,
+        max: 26
+    },
+    usernameL: {
+        type: String,
+        label: "Username in lowercase",
+        regEx: /^[a-z0-9_]+$/
+    },
+    realname: {
+        type: String,
+        label: "Real Name",
+        regEx: /^[A-Za-z0-9 .'-]+$/,
+        optional: true
+    },
+    rank: {
+        type: String,
+        label: "Rank",
+        allowedValues: ["default", "moderator", "admin"]
+    },
+    liked: {
+        type: Array,
+        label: "User's Liked songs"
+    },
+    "liked.$": {
+        type: String,
+        label: "A MID of a song a user liked"
+    },
+    disliked: {
+        type: Array,
+        label: "User's Disliked songs"
+    },
+    "disliked.$": {
+        type: String,
+        label: "A MID of a song a user disliked"
+    },
+    settings: {
+        type: Object,
+        label: "The settings of a user"
+    },
+    "settings.showRating": {
+        type: Boolean,
+        label: "If a user wants their liked and disliked songs to show up for everyone",
+        defaultValue: false
+    },
+    statistics: {
+        type: Object,
+        label: "The statistics of a user"
+    },
+    "statistics.songsRequested": {
+        type: Number,
+        label: "Amount of songs the user has requested",
+        defaultValue: 0
+    }
+});
+
+Schemas.UserPunishments = new SimpleSchema({
+    mute: {
+        type: Object,
+        label: "User's Current Mute Info",
+        optional: true
+    },
+    "mute.mutedBy": {
+        type: String,
+        label: "Muted By"
+    },
+    "mute.mutedAt": {
+        type: Date,
+        label: "Muted At"
+    },
+    "mute.mutedUntil": {
+        type: Date,
+        label: "Muted Until"
+    },
+    mutes: {
+        type: Array,
+        label: "All of the mutes of a user",
+        optional: true
+    },
+    "mutes.$": {
+        type: Object,
+        label: "One of the mutes of a user"
+    },
+    "mutes.$.mutedBy": {
+        type: String,
+        label: "Muted By"
+    },
+    "mutes.$.mutedAt": {
+        type: Date,
+        label: "Muted At"
+    },
+    "mutes.$.mutedUntil": {
+        type: Date,
+        label: "Muted Until"
+    },
+    ban: {
+        type: Object,
+        label: "User's Current Ban Info",
+        optional: true
+    },
+    "ban.bannedBy": {
+        type: String,
+        label: "Banned By"
+    },
+    "ban.bannedAt": {
+        type: Date,
+        label: "Banned At"
+    },
+    "ban.bannedUntil": {
+        type: Date,
+        label: "Banned Until"
+    },
+    bans: {
+        type: Array,
+        label: "All of the bans of a user",
+        optional: true
+    },
+    "bans.$": {
+        type: Object,
+        label: "One of the bans of a user"
+    },
+    "bans.$.bannedBy": {
+        type: String,
+        label: "Banned By"
+    },
+    "bans.$.bannedAt": {
+        type: Date,
+        label: "Banned At"
+    },
+    "bans.$.bannedUntil": {
+        type: Date,
+        label: "Banned Until"
+    }
+});
+
+Schemas.User = new SimpleSchema({
+    username: {
+        type: String,
+        // For accounts-password, either emails or username is required, but not both. It is OK to make this
+        // optional here because the accounts-password package does its own validation.
+        // Third-party login packages may not require either. Adjust this schema as necessary for your usage.
+        optional: true,
+        regEx: /^[a-zA-Z0-9_]+$/,
+        min: 6,
+        max: 26
+    },
+    emails: {
+        type: Array,
+        // For accounts-password, either emails or username is required, but not both. It is OK to make this
+        // optional here because the accounts-password package does its own validation.
+        // Third-party login packages may not require either. Adjust this schema as necessary for your usage.
+        optional: true
+    },
+    "emails.$": {
+        type: Object
+    },
+    "emails.$.address": {
+        type: String,
+        regEx: SimpleSchema.RegEx.Email
+    },
+    "emails.$.verified": {
+        type: Boolean
+    },
+    createdAt: {
+        type: Date
+    },
+    profile: {
+        type: Schemas.UserProfile
+    },
+    punishments: {
+        type: Schemas.UserPunishments,
+        defaultValue: {mutes: [], bans: []}
+    },
+    // Make sure this services field is in your schema if you're using any of the accounts packages
+    services: {
+        type: Object,
+        optional: true,
+        blackbox: true
+    },
+    // In order to avoid an 'Exception in setInterval callback' from Meteor
+    heartbeat: {
+        type: Date,
+        optional: true
+    }
+});
+
+Schemas.Report = new SimpleSchema({
+    room: {
+        type: String,
+        label: "Type of the room that the reports are from",
+        regEx: /^[a-z0-9_]{1,20}$/
+    },
+    report: {
+        type: Array,
+        label: "The reports"
+    },
+    "report.$": {
+        type: Object,
+        label: "A report"
+    },
+    "report.$.song": {
+        type: String,
+        label: "A report's song MID"
+    },
+    "report.$.type": {
+        type: Array,
+        label: "The types of things a song was reported for"
+    },
+    "report.$.type.$": {
+        type: String,
+        label: "A type of thing a report was reported for",
+        allowedValues: ["report-song", "report-title", "report-author", "report-duration", "report-audio", "report-albumart", "report-other"]
+    },
+    "report.$.reason": {
+        type: Array,
+        label: "The reasons a song was reported for"
+    },
+    "report.$.reason.$": {
+        type: String,
+        label: "A reason a song was reported for",
+        allowedValues: ["report-song-not-playing", "report-song-does-not-exist", "report-song-other", "report-title-incorrect", "report-title-inappropriate", "report-title-other", "report-author-incorrect", "report-author-inappropriate", "report-author-other", "report-duration-long", "report-duration-short", "report-duration-other", "report-audio-inappropriate", "report-audio-not-playing", "report-audio-other", "report-albumart-incorrect", "report-albumart-inappropriate", "report-albumart-not-showing", "report-albumart-other"]
+    },
+    "report.$.other": {
+        type: String,
+        label: "Other",
+        optional: true
+    }
+});
+
+Rooms.attachSchema(Schemas.Room);
+Alerts.attachSchema(Schemas.Alert);
+Chat.attachSchema(Schemas.Chat);
+Playlists.attachSchema(Schemas.Playlist);
+Queues.attachSchema(Schemas.Queue);
+Meteor.users.attachSchema(Schemas.User);
+Reports.attachSchema(Schemas.Report);

+ 21 - 59
app/server/server.js

@@ -19,6 +19,8 @@ Meteor.startup(function() {
     emojione.ascii = true;
 });
 
+var default_song = {id: "xKVcVSYmesU", mid: "ABCDEF", likes: 0, dislikes: 0, title: "Immortals", artist: "Fall Out Boy", img: "http://c.directlyrics.com/img/upload/fall-out-boy-sixth-album-cover.jpg", type: "YouTube", duration: 181, skipDuration: 0, requestedBy: "NONE", approvedBy: "NONE"};
+
 Alerts.update({active: true}, {$set: {active: false}}, { multi: true });
 
 var stations = [];
@@ -96,40 +98,12 @@ function getStation(type, cb) {
 
 function createRoom(display, tag, private) {
     var type = tag;
-    if (Rooms.find({type: type}).count() === 0 && private === false) {
-        Rooms.insert({display: display, type: type, users: 0}, function(err) {
-            if (err) {
-                throw err;
-            } else {
-                if (Playlists.find({type: type}).count() === 1) {
-                    stations.push(new Station(type));
-                } else {
-                    Playlists.insert({type: type, songs: getSongsByType(type)}, function (err2) {
-                        if (err2) {
-                            throw err2;
-                        } else {
-                            stations.push(new Station(type));
-                        }
-                    });
-                }
-            }
-        });
-    } else if (Rooms.find({type: type}).count() === 0 && private === true) {
-        Rooms.insert({display: display, type: type, users: 0, private: true}, function(err) {
+    if (Rooms.find({type: type}).count() === 0) {
+        Rooms.insert({display: display, type: type, users: 0, private: private, currentSong: {song: default_song, started: 0}}, function(err) {
             if (err) {
                 throw err;
             } else {
-                if (Playlists.find({type: type}).count() === 1) {
-                    stations.push(new Station(type));
-                } else {
-                    Playlists.insert({type: type, songs: getSongsByType(type)}, function (err2) {
-                        if (err2) {
-                            throw err2;
-                        } else {
-                            stations.push(new Station(type));
-                        }
-                    });
-                }
+                stations.push(new Station(type));
             }
         });
     } else {
@@ -138,6 +112,11 @@ function createRoom(display, tag, private) {
 }
 
 function Station(type) {
+    if (Playlists.find({type: type}).count() === 0) {
+        Playlists.insert({type: type, songs: [default_song], lastSong: 0});
+    } else if (Playlists.findOne({type: type}).songs.length === 0) {
+        Playlists.update({type: type}, {$push: {songs: default_song}});
+    }
     Meteor.publish(type, function() {
         return undefined;
     });
@@ -146,18 +125,14 @@ function Station(type) {
     var playlist = Playlists.findOne({type: type});
     var songs = playlist.songs;
 
-    if (playlist.lastSong === undefined) {
-        Playlists.update({type: type}, {$set: {lastSong: 0}});
-        playlist = Playlists.findOne({type: type});
-        songs = playlist.songs;
-    }
     var currentSong = playlist.lastSong;
     if (currentSong < (songs.length - 1)) {
         currentSong++;
     } else currentSong = 0;
     var currentTitle = songs[currentSong].title;
 
-    Rooms.update({type: type}, {$set: {currentSong: {song: songs[currentSong], started: startedAt}, users: 0}});
+    var res = Rooms.update({type: type}, {$set: {currentSong: {song: songs[currentSong], started: startedAt}, users: 0}});
+    console.log(res);
 
     this.skipSong = function() {
         self.voted = [];
@@ -339,6 +314,7 @@ function getSongDuration(query, artistName){
             }
         }
     }
+    return 0;
 }
 
 function getSongAlbumArt(query, artistName){
@@ -359,29 +335,10 @@ function getSongAlbumArt(query, artistName){
 //var room_types = ["edm", "nightcore"];
 var songsArr = [];
 
-function getSongsByType(type) {
-    if (type === "edm") {
-        return [
-            {id: "aE2GCa-_nyU", mid: "fh6_Gf", title: "Radioactive", duration: getSongDuration("Radioactive - Lindsey Stirling and Pentatonix", "Lindsey Stirling, Pentatonix"), artist: "Lindsey Stirling, Pentatonix", type: "YouTube", img: "https://i.scdn.co/image/62167a9007cef2e8ef13ab1d93019312b9b03655"},
-            {id: "aHjpOzsQ9YI", mid: "goG88g", title: "Crystallize", artist: "Lindsey Stirling", duration: getSongDuration("Crystallize", "Lindsey Stirling"), type: "YouTube", img: "https://i.scdn.co/image/b0c1ccdd0cd7bcda741ccc1c3e036f4ed2e52312"}
-        ];
-    } else if (type === "nightcore") {
-        return [{id: "f7RKOP87tt4", mid: "5pGGog", title: "Monster (DotEXE Remix)", duration: getSongDuration("Monster (DotEXE Remix)", "Meg & Dia"), artist: "Meg & Dia", type: "YouTube", img: "https://i.scdn.co/image/35ecdfba9c31a6c54ee4c73dcf1ad474c560cd00"}];
-    } else {
-        return [{id: "dQw4w9WgXcQ", mid: "6_fdr4", title: "Never Gonna Give You Up", duration: getSongDuration("Never Gonna Give You Up", "Rick Astley"), artist: "Rick Astley", type: "YouTube", img: "https://i.scdn.co/image/5246898e19195715e65e261899baba890a2c1ded"}];
-    }
-}
-
 Rooms.find({}).fetch().forEach(function(room) {
     var type = room.type;
     if (Playlists.find({type: type}).count() === 0) {
-        if (type === "edm") {
-            Playlists.insert({type: type, songs: getSongsByType(type)});
-        } else if (type === "nightcore") {
-            Playlists.insert({type: type, songs: getSongsByType(type)});
-        } else {
-            Playlists.insert({type: type, songs: getSongsByType(type)});
-        }
+        Playlists.insert({type: type, songs: []});
     }
     if (Playlists.findOne({type: type}).songs.length === 0) {
         // Add a global video to Playlist so it can proceed
@@ -877,7 +834,7 @@ Meteor.methods({
                     Queues.insert({type: type, songs: []});
                 }
                 if (songData !== undefined && Object.keys(songData).length === 5 && songData.type !== undefined && songData.title !== undefined && songData.artist !== undefined && songData.img !== undefined) {
-                    songData.duration = getSongDuration(songData.title, songData.artist) || 0;
+                    songData.duration = Number(getSongDuration(songData.title, songData.artist));
                     songData.img = getSongAlbumArt(songData.title, songData.artist) || "";
                     songData.skipDuration = 0;
                     songData.likes = 0;
@@ -997,12 +954,17 @@ Meteor.methods({
                                 type: songData.type,
                                 likes: Number(songData.likes),
                                 dislikes: Number(songData.dislikes),
-                                requesedBy: songData.requestedBy,
+                                requestedBy: songData.requestedBy,
                                 approvedBy: Meteor.userId()
                             }
                         }
                     });
                     Queues.update({type: type}, {$pull: {songs: {mid: songData.mid}}});
+                    getStation(type, function(station) {
+                        if (station === undefined) {
+                            stations.push(new Station(type));
+                        }
+                    });
                     return true;
                 } else {
                     throw new Meteor.Error(403, "Invalid data.");