diff --git a/src/schemas/channel/channelSchema.js b/src/schemas/channel/channelSchema.js
index f98332e..235023f 100644
--- a/src/schemas/channel/channelSchema.js
+++ b/src/schemas/channel/channelSchema.js
@@ -64,6 +64,10 @@ const channelSchema = new mongoose.Schema({
required: true,
default: true
},
+ streamURL: {
+ type: mongoose.SchemaTypes.String,
+ default: ''
+ }
},
permissions: {
type: channelPermissionSchema,
diff --git a/src/utils/media/yanker.js b/src/utils/media/yanker.js
index 09cb486..02f9193 100644
--- a/src/utils/media/yanker.js
+++ b/src/utils/media/yanker.js
@@ -32,8 +32,15 @@ module.exports.yankMedia = async function(url, title){
//return media object list from IA module
return await iaUtil.fetchMetadata(pullType.id, title);
case "yt":
- //return mediao object list from the YT-DLP module's youtube function
+ //return media object list from the YT-DLP module's youtube function
return await ytdlpUtil.fetchYoutubeMetadata(pullType.id, title);
+ case "ytp":
+ //return media object list from YT-DLP module's youtube playlist function
+ //return await ytdlpUtil.fetchYoutubePlaylistMetadata(pullType.id, title);
+ //Holding off on this since YT-DLP takes 10 years to do a playlist as it needs to pull each and every video one-by-one
+ //Maybe in the future a piped alternative might be in order, however this would most likely require us to host our own local instance.
+ //Though it could give us added resistance against youtube/google's rolling IP bans
+ return null;
case "dm":
//return mediao object list from the YT-DLP module's dailymotion function
return await ytdlpUtil.fetchDailymotionMetadata(pullType.id, title);
@@ -85,7 +92,7 @@ module.exports.getMediaType = async function(url){
//If we have link to a resource from archive.org
if(match = url.match(/archive\.org\/(?:details|download)\/([a-zA-Z0-9\/._-\s\%]+)/)){
- //return internet archive code
+ //return internet archive upload id and filepath
return {
type: "ia",
id: match[1]
@@ -101,6 +108,15 @@ module.exports.getMediaType = async function(url){
}
}
+ //If we have a match to a youtube playlist
+ if((match = url.match(/youtube\.com\/playlist\?list=([a-zA-Z0-9_-]{34})/)) || (match = url.match(/youtu\.be\/playlist\?list=([a-zA-Z0-9_-]{34})/))){
+ //return youtube playlist id
+ return {
+ type: "ytp",
+ id: match[1]
+ }
+ }
+
//If we have a match to a dailymotion video
if(match = url.match(/dailymotion\.com\/video\/([a-z0-9]{7})/)){
return {
diff --git a/src/utils/media/ytdlpUtils.js b/src/utils/media/ytdlpUtils.js
index af5805d..c487b86 100644
--- a/src/utils/media/ytdlpUtils.js
+++ b/src/utils/media/ytdlpUtils.js
@@ -32,7 +32,28 @@ const loggerUtils = require('../loggerUtils.js')
module.exports.fetchYoutubeMetadata = async function(id, title){
try{
//Try to pull media from youtube id
- const media = await fetchMetadata(`https://youtu.be/${id}`, title, 'yt');
+ const media = await fetchVideoMetadata(`https://youtu.be/${id}`, title, 'yt');
+
+ //Return found media
+ return media;
+ //If something went wrong
+ }catch(err){
+ //If our IP was banned by youtube
+ if(err.message.match("Sign in to confirm you’re not a bot.")){
+ //Make our own error with blackjack and hookers
+ throw loggerUtils.exceptionSmith("The server's IP address has been banned by youtube. Please contact your server's administrator.", "queue");
+ //Otherwise if we don't have a good way to handle it
+ }else{
+ //toss it back up
+ throw err;
+ }
+ }
+}
+
+module.exports.fetchYoutubePlaylistMetadata = async function(id, title){
+ try{
+ //Try to pull media from youtube id
+ const media = await fetchPlaylistMetadata(`https://youtu.be/playlist?list=${id}`, title, 'yt');
//Return found media
return media;
@@ -52,22 +73,19 @@ module.exports.fetchYoutubeMetadata = async function(id, title){
module.exports.fetchDailymotionMetadata = async function(id, title){
//Pull media from dailymotion link
- const media = await fetchMetadata(`https://dailymotion.com/video/${id}`, title, 'dm');
+ const media = await fetchVideoMetadata(`https://dailymotion.com/video/${id}`, title, 'dm');
//Return found media;
return media;
}
-//Generic YTDLP function meant to be used by service-sepecific fetchers which will then be used to fetch video metadata
-async function fetchMetadata(link, title, type, format = 'b'){
+//Generic single video YTDLP function meant to be used by service-sepecific fetchers which will then be used to fetch video metadata
+async function fetchVideoMetadata(link, title, type, format = 'b'){
//Create media list
const mediaList = [];
- //Pull raw metadata
- const rawMetadata = await ytdlp(link, {
- dumpSingleJson: true,
- format
- });
+ //Pull raw metadata from YT-DLP
+ const rawMetadata = await ytdlpFetch(link, format);
//Pull data from rawMetadata, sanatizing title to prevent XSS
const name = validator.escape(validator.trim(rawMetadata.title));
@@ -85,4 +103,17 @@ async function fetchMetadata(link, title, type, format = 'b'){
//Return list of media
return mediaList;
+}
+
+//YT-DLP takes forever to handle playlists, we'll handle this via piped in the future perhaps
+/*async function fetchPlaylistMetadata(link, title, type, format = 'b'){
+}*/
+
+//Wrapper function for YT-DLP NPM package with pre-set cli-flags
+async function ytdlpFetch(link, format = 'b'){
+ //return promise from ytdlp
+ return ytdlp(link, {
+ dumpSingleJson: true,
+ format
+ });
}
\ No newline at end of file
diff --git a/src/validators/channelValidator.js b/src/validators/channelValidator.js
index b0390a3..60c9170 100644
--- a/src/validators/channelValidator.js
+++ b/src/validators/channelValidator.js
@@ -80,6 +80,11 @@ module.exports.settingsMap = function(){
optional: true,
isBoolean: true,
errorMessage: "Bad channel settings map."
+ },
+ 'settingsMap.streamURL': {
+ optional: true,
+ isURL: true,
+ errorMessage: "Invalid Stream URL"
}
})
);
diff --git a/src/views/partial/channelSettings/settings.ejs b/src/views/partial/channelSettings/settings.ejs
index b54b138..b2bd804 100644
--- a/src/views/partial/channelSettings/settings.ejs
+++ b/src/views/partial/channelSettings/settings.ejs
@@ -20,7 +20,14 @@ along with this program. If not, see . %>
<% Object.keys(channel.settings).forEach((key) => { %>
- class="channel-preference-list-item" type="checkbox" <% if(channel.settings[key]){ %> checked <% } %>>
+ <% switch(typeof channel.settings[key]){
+ case "string": %>
+ class="channel-preference-list-item" value="<%- channel.settings[key] %>">
+ <% break;
+ default: %>
+ class="channel-preference-list-item" type="checkbox" <% if(channel.settings[key]){ %> checked <% } %>>
+ <% break;
+ } %>
<% }); %>
diff --git a/www/js/channelSettings.js b/www/js/channelSettings.js
index a98bd08..980b1cb 100644
--- a/www/js/channelSettings.js
+++ b/www/js/channelSettings.js
@@ -235,13 +235,28 @@ class prefrenceList{
}
async submitUpdate(event){
+ //Get key from event target
const key = event.target.id.replace("channel-preference-","");
- const value = event.target.checked;
+
+ //Pull value from event target
+ let value = event.target.value;
+
+ //If this is a checkmark
+ if(event.target.type == "checkbox"){
+ //Use the .checked property instead of .value
+ value = event.target.checked;
+ }
+
+ //Create settings map
const settingsMap = new Map([
[key, value]
]);
- this.handleUpdate(await utils.ajax.setChannelSetting(this.channel, settingsMap), event.target, key);
+ //Send update and collect results
+ const update = await utils.ajax.setChannelSetting(this.channel, settingsMap);
+
+ //Handle update from server
+ this.handleUpdate(update, event.target, key);
}
handleUpdate(data, target, key){