/* This file is part of FreeTube. FreeTube is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. FreeTube is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FreeTube. If not, see . */ /* * File for all functions related to subscriptions. */ let subscriptionTimer; let forceTimer; let checkSubscriptions = true; let forceSubs = true; let displaySubsLock = false; /** * Add a channel to the user's subscription database. * * @param {string} channelId - The channel ID to add to the subscriptions database. * * @return {Void} */ function addSubscription(data, useToast = true, profile = '') { ft.log('Channel Data: ', data); let newSubscription = data; newSubscription.profile = [{ value: profile }]; if (profile === '') { newSubscription.profile = [{ value: profileSelectView.activeProfile.name }]; } subDb.find({ channelId: data.channelId }, (err, docs) => { if (jQuery.isEmptyObject(docs)) { subDb.insert(newSubscription, (err, newDoc) => { if (useToast) { showToast('Added ' + newSubscription.channelName + ' to subscriptions.'); } // Refresh the list of subscriptions on the side navigation bar. displaySubs(); }); } else { subDb.update({ channelId: data.channelId, }, { $push: { profile: { value: newSubscription.profile[0].value } } }, (err, numAdded) => { // Refresh the list of subscriptions on the side navigation bar. displaySubs(); showToast('Added ' + data.channelName + ' to subscriptions.'); }); } }); } /** * Remove a channel from the subscriptions database. * * @param {string} channelId - The channel ID to be removed. * * @return {Void} */ function removeSubscription(channelId, profile = profileSelectView.activeProfile.name, displayToast = true) { subDb.find({ channelId: channelId }, (err, docs) => { if (docs[0].profile.length > 1 && profile !== 'All Profiles') { subDb.update({ channelId: channelId, }, { $pull: { profile: { value: profile } } }, { multi: true },(err, numRemoved) => { // Refresh the list of subscriptions on the side navigation bar. displaySubs(); if (displayToast) { showToast('Removed channel from subscriptions.'); } }); } else { subDb.remove({ channelId: channelId }, {}, (err, numRemoved) => { // Refresh the list of subscriptions on the side navigation bar. displaySubs(); if (displayToast) { showToast('Removed channel from subscriptions.'); } }); } }); } /** * Load the recent uploads of the user's subscriptions. * * @return {Void} */ function loadSubscriptions() { if (checkSubscriptions === false && subscriptionView.videoList.length > 0) { ft.log('Will not load subscriptions. Timer still on.'); loadingView.seen = false; return; } else { showToast('Refreshing Subscription List. Please wait…'); checkSubscriptions = false; progressView.seen = true; $('#reloadButton')[0].classList.add('fa-spin'); } let videoList = []; const subscriptions = returnSubscriptions(); subscriptions.then((results) => { let channelId = ''; let videoList = []; if (results.length > 0) { let counter = 0; for (let i = 0; i < results.length; i++) { channelId = results[i]['channelId']; invidiousAPI('channels/latest', channelId, {}, (data) => { data.forEach((video, index) => { data[index].author = results[i]['channelName']; data[index].profile = results[i]['profile']; historyDb.findOne({ videoId: video.videoId }, function (err, doc) { if (doc === null) { data[index].watched = false } else { data[index].watched = true; } }); }); videoList = videoList.concat(data); counter = counter + 1; progressView.progressWidth = (counter / results.length) * 100; if (counter === results.length) { addSubsToView(videoList); } }, (errorData) => { showToast('Unable to load channel: ' + results[i]['channelName']); counter = counter + 1; progressView.progressWidth = (counter / results.length) * 100; if (counter === results.length) { addSubsToView(videoList); } }); } } else { // User has no subscriptions. Display message. loadingView.seen = false; headerView.seen = false; noSubscriptions.seen = true; $('#reloadButton')[0].classList.remove('fa-spin'); } }); } function addSubsToView(videoList) { subscriptionView.fullVideoList = videoList; videoList = videoList.filter(a => { return !a.premium; }); if (hideWatchedSubs) { videoList = videoList.filter(a => { return !a.watched; }); } if (profileSelectView.activeProfile.name !== 'All Channels') { videoList = videoList.filter(a => { return a.profile.map(x => x.value).indexOf(profileSelectView.activeProfile.name) !== -1 }); } videoList.sort((a, b) => { return b.published - a.published; }); subscriptionView.videoList = []; if (videoList.length > 100) { for (let i = 0; i < 100; i++) { displayVideo(videoList[i], 'subscriptions'); } } else { videoList.forEach((video) => { displayVideo(video, 'subscriptions'); }); } loadingView.seen = false; progressView.seen = false; progressView.progressWidth = 0; $('#reloadButton')[0].classList.remove('fa-spin'); subscriptionTimer = window.setTimeout(() => { checkSubscriptions = true; }, 7200000); ft.log('Done'); } function forceSubscriptions() { if (progressView.progressWidth > 0) { showToast('Please wait for subscriptions to finish loading before trying again.'); return; } else if (forceSubs === false) { showToast('Too many attempts. Please wait before loading subscriptions again.'); return; } else { window.clearTimeout(subscriptionTimer); checkSubscriptions = true; loadSubscriptions(); forceSubs = false; } forceTimer = window.setTimeout(() => { forceSubs = true; }, 300000); } /** * Get the list of subscriptions from the user's subscription database. * * @return {promise} The list of subscriptions. */ function returnSubscriptions() { return new Promise((resolve, reject) => { subDb.find({}, (err, subs) => { resolve(subs); }); }); } /** * Display the list of subscriptions on the side navigation bar. * * @return {Void} */ function displaySubs() { if (displaySubsLock) { return; } displaySubsLock = true; const subList = document.getElementById('subscriptions'); subList.innerHTML = ''; // Sort alphabetically subDb.find().sort({ channelName: 1 }).exec((err, subs) => { let subFilter; if (profileSelectView.activeProfile.name === 'All Channels' || typeof(profileSelectView.activeProfile.name) === 'undefined') { subFilter = subs; } else { subFilter = subs.filter(a => { return a.profile.find((name) => {return name.value === profileSelectView.activeProfile.name }); }); } subFilter.forEach((channel) => { // Grab subscriptions.html to be used as a template. const subsTemplate = require('./templates/subscriptions.html') mustache.parse(subsTemplate); const rendered = mustache.render(subsTemplate, { channelIcon: channel['channelThumbnail'], channelName: channel['channelName'], channelId: channel['channelId'], }); // Render template to page. const subscriptionsHtml = $('#subscriptions').html(); $('#subscriptions').html(subscriptionsHtml + rendered); }); displaySubsLock = false; }); // Add onclick function $('#subscriptions .fa-times').onClick = removeSubscription; } /** * Adds / Removes a subscription based on if the channel is in the database or not. * @param {string} channelId - The channel ID to check * * @return {Void} */ function toggleSubscription(data) { event.stopPropagation(); const checkIfSubscribed = isSubscribed(data.channelId); checkIfSubscribed.then((results) => { if (results === false) { playerView.subscribedText = 'UNSUBSCRIBE'; channelView.subButtonText = 'UNSUBSCRIBE'; addSubscription(data); } else { playerView.subscribedText = 'SUBSCRIBE'; channelView.subButtonText = 'SUBSCRIBE'; removeSubscription(data.channelId); } }); } /** * Check if the user is subscribed to a channel or not. * * @param {string} channelId - The channel ID to check * * @return {promise} - A boolean value if the channel is currently subscribed or not. */ function isSubscribed(channelId, profile = profileSelectView.activeProfile.name) { return new Promise((resolve, reject) => { subDb.find({ $and: [{ channelId: channelId }, { profile: { $elemMatch: { value: profile } } }, ] }, (err, docs) => { if (jQuery.isEmptyObject(docs)) { resolve(false); } else { resolve(true); } }); }); }