Added stream URL to channel settings.

This commit is contained in:
rainbow napkin 2025-05-10 22:07:20 -04:00
parent e4bebce431
commit 93265b7890
6 changed files with 92 additions and 14 deletions

View file

@ -64,6 +64,10 @@ const channelSchema = new mongoose.Schema({
required: true, required: true,
default: true default: true
}, },
streamURL: {
type: mongoose.SchemaTypes.String,
default: ''
}
}, },
permissions: { permissions: {
type: channelPermissionSchema, type: channelPermissionSchema,

View file

@ -32,8 +32,15 @@ module.exports.yankMedia = async function(url, title){
//return media object list from IA module //return media object list from IA module
return await iaUtil.fetchMetadata(pullType.id, title); return await iaUtil.fetchMetadata(pullType.id, title);
case "yt": 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); 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": case "dm":
//return mediao object list from the YT-DLP module's dailymotion function //return mediao object list from the YT-DLP module's dailymotion function
return await ytdlpUtil.fetchDailymotionMetadata(pullType.id, title); 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 we have link to a resource from archive.org
if(match = url.match(/archive\.org\/(?:details|download)\/([a-zA-Z0-9\/._-\s\%]+)/)){ 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 { return {
type: "ia", type: "ia",
id: match[1] 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 we have a match to a dailymotion video
if(match = url.match(/dailymotion\.com\/video\/([a-z0-9]{7})/)){ if(match = url.match(/dailymotion\.com\/video\/([a-z0-9]{7})/)){
return { return {

View file

@ -32,7 +32,28 @@ const loggerUtils = require('../loggerUtils.js')
module.exports.fetchYoutubeMetadata = async function(id, title){ module.exports.fetchYoutubeMetadata = async function(id, title){
try{ try{
//Try to pull media from youtube id //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 youre 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 found media
return media; return media;
@ -52,22 +73,19 @@ module.exports.fetchYoutubeMetadata = async function(id, title){
module.exports.fetchDailymotionMetadata = async function(id, title){ module.exports.fetchDailymotionMetadata = async function(id, title){
//Pull media from dailymotion link //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 found media;
return media; return media;
} }
//Generic YTDLP function meant to be used by service-sepecific fetchers which will then be used to fetch video metadata //Generic single video 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'){ async function fetchVideoMetadata(link, title, type, format = 'b'){
//Create media list //Create media list
const mediaList = []; const mediaList = [];
//Pull raw metadata //Pull raw metadata from YT-DLP
const rawMetadata = await ytdlp(link, { const rawMetadata = await ytdlpFetch(link, format);
dumpSingleJson: true,
format
});
//Pull data from rawMetadata, sanatizing title to prevent XSS //Pull data from rawMetadata, sanatizing title to prevent XSS
const name = validator.escape(validator.trim(rawMetadata.title)); const name = validator.escape(validator.trim(rawMetadata.title));
@ -86,3 +104,16 @@ async function fetchMetadata(link, title, type, format = 'b'){
//Return list of media //Return list of media
return mediaList; 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
});
}

View file

@ -80,6 +80,11 @@ module.exports.settingsMap = function(){
optional: true, optional: true,
isBoolean: true, isBoolean: true,
errorMessage: "Bad channel settings map." errorMessage: "Bad channel settings map."
},
'settingsMap.streamURL': {
optional: true,
isURL: true,
errorMessage: "Invalid Stream URL"
} }
}) })
); );

View file

@ -20,7 +20,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<% Object.keys(channel.settings).forEach((key) => { %> <% Object.keys(channel.settings).forEach((key) => { %>
<span class="admin-list-field-container"> <span class="admin-list-field-container">
<label class="admin-list-label"><%- key %>:</label> <label class="admin-list-label"><%- key %>:</label>
<% switch(typeof channel.settings[key]){
case "string": %>
<input id=<%- `channel-preference-${key}` %> class="channel-preference-list-item" value="<%- channel.settings[key] %>">
<% break;
default: %>
<input id=<%- `channel-preference-${key}` %> class="channel-preference-list-item" type="checkbox" <% if(channel.settings[key]){ %> checked <% } %>> <input id=<%- `channel-preference-${key}` %> class="channel-preference-list-item" type="checkbox" <% if(channel.settings[key]){ %> checked <% } %>>
<% break;
} %>
</span> </span>
<% }); %> <% }); %>
</form> </form>

View file

@ -235,13 +235,28 @@ class prefrenceList{
} }
async submitUpdate(event){ async submitUpdate(event){
//Get key from event target
const key = event.target.id.replace("channel-preference-",""); 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([ const settingsMap = new Map([
[key, value] [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){ handleUpdate(data, target, key){