discord-yt-poster
Version:
An automated Youtube Poster package, with a Database included, which allows you to make it easy to post new Video Uploads or Streams to a Channel, per YT-CHANNEl defineable, fast, relyable, FETCHING ALL CHANNEL DATA AND ALL VIDEOS!
424 lines (397 loc) • 22.6 kB
JavaScript
/*
MIT License
Copyright (c) 2021 Tomato6966 (chris.pre03@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
//Import the Utils
const Discord = require("discord.js");
const Parser = require("rss-parser");
const parser = new Parser();
const colors = require("colors")
const YoutubeLogger = require('./youtubelogger.js');
const Util = require('./Util.js');
const CI = require('./channelInfo.js');
const Enmap = require("enmap");
//swap the provider: https://josh.evie.dev/providers/about
//MAKE SURE TO INSTALL THE GIVEN PROVIDER TOO!, @joshdb/json @joshdb/mongo are already installed but sqlite etc. not, this is to support REPLIT etc.
class YoutubePoster {
/**
* @param {DiscordBotClient} client A Discord Bot Client, make sure it's ready otherwise you might face some sort of bugs
* @param {OBJECT} options Options for the YoutubePoster
* EXAMPLE OPTIONS:
* {
loop_delays_in_min: 3, //send a new video every 6 minutes, you can do 1 to be fast, but the more channels to check the more slower your bot will get + If you do tooo much youtube Requesting, then you might get IP BANNED (this is not tested/proven)
defaults: {
Notification: "<@{discorduser}> Posted: **{videotitle}**, as \`{videoauthorname}\`\n{videourl}"
},
}
*/
constructor(client, options) {
//get the log string
this.ytp_log = ` >-Discord-YT-Poster-< `.dim.red;
this.warn_log = `[WARN] `.yellow;
this.info_log = `[INFO] `.cyan;
//set the options
if (!client) {
throw new Error("No Valid DiscordClient Added")
}
this.client = client;
this.options = {
loop_delays_in_min: 5,
defaults: {
Notification: "<@{discorduser}> Posted: **{videotitle}**, as \`{videoauthorname}\`\n{videourl}"
},
}
//set the global Youtube Poster variable for the version
this.version = require("../package.json").version;
//set the author variable for the creators
this.author = "Tomato6966";
//set the author discord Support Server
this.discord = "https://discord.gg/FQGXbypRf8";
//set the author variable for the creators
this.github = "https://github.com/Tomato6966/discord-yt-poster/wiki";
//loop through the custom object
this.checkOptions(options);
//if no method added, use this, throw error
if (!this.constructor && !this.constructor.name) {
throw new Error(`The ${this.constructor.name} class may not be instantiated!`);
}
//Create the Provider
this.YTP_DB = new Enmap({ name: "Discord-Youtube-Poster" })
//show information
console.log(this.ytp_log + this.info_log + `Successfully created the Discord-Youtube-Poster instance!`.dim.green);
console.log(this.ytp_log + this.info_log + `Note that since v3 it doesn't support MongoDb anymore, this is to fix bugs!`.dim.yellow);
//create the logger
YoutubeLogger(this)
}
checkOptions(options) {
if (options) {
if(options.loop_delays_in_min || options.loop_delays_in_min == 0){
if(typeof options.loop_delays_in_min != "number") throw new SyntaxError(`${`options.loop_delays_in_min`.bold}must be a NUMBER, you provided: ${`${typeof options.loop_delays_in_min}`.bold}`)
let dela = Number(options.loop_delays_in_min)
if(dela < 0) throw new SyntaxError(`${`options.loop_delays_in_min`.bold} must be ${`BIGGER or EQUAL then 0`.bold}, you provided: ${`${options.loop_delays_in_min}`.bold}`)
if(dela > 59) throw new SyntaxError(`${`options.loop_delays_in_min`.bold} must be ${`SMALLER then 0`.bold}, you provided: ${`${options.loop_delays_in_min}`.bold}`)
//round it!
dela = Math.round(dela);
//set the new loop delay
this.options.loop_delays_in_min = dela;
console.log(this.ytp_log + this.info_log + `Using custom ${`options.loop_delays_in_min`.bold}: ${this.options.loop_delays_in_min} ${this.options.loop_delays_in_min == 0 ? "\n" + this.ytp_log + this.info_log + "Tho it's 0, it will only check every 15 Seconds, otherwise you would spam to MUCH!".dim.yellow : ""}`.dim.green);
}
if(options.defaults){
if(options.defaults.Notification){
this.options.defaults.Notification = options.defaults.Notification;
console.log(this.ytp_log + this.info_log + `Using custom ${`options.defaults#Notification`.bold}: ${this.options.defaults.Notification}`.dim.green);
}
}
return this;
} else {
return this;
}
}
/** Set a new YTChannel to a Guild ID
* @param {string} ChannelLink Youtube Channel Link, example: https://youtube.com/c/Tomato6966
* @param {OBJECT|DiscordChannel} DiscordChannel DiscordChannel with ID && guild parameters
* @param {OBJECT|DiscordUser} DiscordUser DiscordUser with ID parameter. who owns the Link
* @param {string} Notification Notification Message | OPTIONAL | DEFAULT: uses the options
* @param {Boolean} preventDuplicates Default: True
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-setChannel
*/
async setChannel(ChannelLink, DiscordChannel, DiscordUser, Notification = this.options.defaults.Notification, preventDuplicates = true) {
return new Promise(async (res, rej) => {
try {
if (!ChannelLink) return rej("A String is required for the ChannelLink");
if (typeof ChannelLink !== "string") return rej(`Passed in ${typeof ChannelLink} but a String would be required for the ChannelLink`);
if (!Util.isValidURL(ChannelLink)) return rej(`${ChannelLink} is not a Valid URL (YT)`);
if (!DiscordChannel || !DiscordChannel.guild || !DiscordChannel.id) return rej("A DiscordChannel with Guild Information is required!");
if(!this.YTP_DB.has(DiscordChannel.guild.id)){
this.YTP_DB.ensure(DiscordChannel.guild.id, {
channels: []
});
}
let channels = this.YTP_DB.get(`${DiscordChannel.guild.id}`, `channels`);
let CHdata = channels.find(v => v.YTchannel.split("/")[v.YTchannel.split("/").length - 1] == ChannelLink.split("/")[ChannelLink.split("/").length - 1])
if (preventDuplicates && CHdata) {
rej(`Channel already setup for the Guild: ${DiscordChannel.guild.id} yet:\n` + JSON.stringify(CHdata, null, 3))
return;
}
let newChannelData = {
YTchannel: ChannelLink,
DiscordGuild: DiscordChannel.guild.id,
DiscordChannel: DiscordChannel.id,
DiscordUser: DiscordUser ? DiscordUser.id : "",
oldvid: "",
alrsent: [],
message: Notification
}
channels.push(newChannelData)
this.YTP_DB.set(`${DiscordChannel.guild.id}`, channels, `channels`);
let data = this.YTP_DB.get(`${DiscordChannel.guild.id}`, `channels`);
var Obj = {};
Obj = newChannelData;
Obj.allChannels = data;
return res(Obj);
} catch (error) {
return rej(error);
}
})
}
/** Get Channel Information about a LINK
* @param {string} ChannelLink Youtube Channel Link, example: https://youtube.com/c/Tomato6966
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-getChannelInfo
*/
async getChannelInfo(ChannelLink) {
return new Promise(async (res, rej) => {
try {
if (!ChannelLink) return rej("A String is required for the ChannelLink");
if (typeof ChannelLink !== "string") return rej(`Passed in ${typeof ChannelLink} but a String would be required for the ChannelLink`);
if (!Util.isValidURL(ChannelLink)) return rej(`${ChannelLink} is not a Valid URL (YT)`);
let channel = await CI.channelInfo(ChannelLink);
if (!channel) return rej("NO INFORMATION FOUND")
return res(channel);
} catch (error) {
return rej(error);
}
})
}
/** Get Videos of a LINK
* @param {string} ChannelLink Youtube Channel Link, example: https://youtube.com/c/Tomato6966
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-getLatestVideos
*/
async getLatestVideos(ChannelLink) {
return new Promise(async (res, rej) => {
try {
if (!ChannelLink) return rej("A String is required for the ChannelLink");
if (typeof ChannelLink !== "string") return rej(`Passed in ${typeof ChannelLink} but a String would be required for the ChannelLink`);
if (!Util.isValidURL(ChannelLink)) return rej(`${ChannelLink} is not a Valid URL (YT)`);
let channel = await CI.channelInfo(ChannelLink);
if (!channel) return rej("NO CHANNEL INFORMATION FOUND")
let content = await parser.parseURL(`https://www.youtube.com/feeds/videos.xml?channel_id=${channel.id}`);
content = content.items.map(v => {
var OBJ = {}
OBJ.title = v.title
OBJ.link = v.link
OBJ.pubDate = v.pubDate
OBJ.author = v.author
OBJ.id = v.link.split("watch?v=")[1] || v.id,
OBJ.isoDate = v.isoDate
return OBJ;
})
let tLastVideos = content.sort((a, b) => {
let aPubDate = new Date(a.pubDate || 0).getTime();
let bPubDate = new Date(b.pubDate || 0).getTime();
return bPubDate - aPubDate;
});
if (tLastVideos.length == 0) return rej("No Videos posted yet")
return res(tLastVideos);
} catch (error) {
return rej(error);
}
})
}
/** Get YTChannel for LINK
* @param {string} DiscordGuildID Discord Guild id
* @param {string} ChannelLink Youtube Channel Link, example: https://youtube.com/c/Tomato6966
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-getChannel
*/
async getChannel(DiscordGuildID, ChannelLink) {
return new Promise(async (res, rej) => {
try {
if (!DiscordGuildID) return rej("A String is required for the DiscordGuildID");
if (typeof DiscordGuildID !== "string" || DiscordGuildID.length != 18) return rej(`Passed in ${typeof DiscordGuildID} but a String would be required for the DiscordGuildID`);
if (!ChannelLink) return rej("A String is required for the ChannelLink");
if (typeof ChannelLink !== "string") return rej(`Passed in ${typeof ChannelLink} but a String would be required for the ChannelLink`);
if (!Util.isValidURL(ChannelLink)) return rej(`${ChannelLink} is not a Valid URL (YT)`);
if(!this.YTP_DB.has(DiscordGuildID)){
this.YTP_DB.ensure(DiscordGuildID, {
channels: []
});
}
let channels = this.YTP_DB.get(`${DiscordGuildID}`, `channels`);
let CHdata = channels.find(v => v.YTchannel.split("/")[v.YTchannel.split("/").length - 1] == ChannelLink.split("/")[ChannelLink.split("/").length - 1])
if (!CHdata) {
CHdata = "Channel not setup yet";
return rej(CHdata);
}
return res(CHdata);
} catch (error) {
return rej(error);
}
})
}
/** Get YTChannels for User
* @param {string} DiscordGuildID Discord Guild id
* @param {OBJECT|DiscordUser} DiscordUser DiscordUser with ID parameter
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-getChannels4User
*/
async getChannels4User(DiscordGuildID, DiscordUser) {
return new Promise(async (res, rej) => {
try {
if (!DiscordGuildID) return rej("A String is required for the DiscordGuildID");
if (typeof DiscordGuildID !== "string" || DiscordGuildID.length != 18) return rej(`Passed in ${typeof DiscordGuildID} but a String would be required for the DiscordGuildID`);
if (!DiscordUser || !DiscordUser.id) return rej("No User with a Valid ID added for DiscordUser");
if(!this.YTP_DB.has(DiscordGuildID)){
this.YTP_DB.ensure(DiscordGuildID, {
channels: []
});
}
let channels = this.YTP_DB.get(`${DiscordGuildID}`, `channels`);
let CHdata = channels.filter(v => v.DiscordUser == DiscordUser.id)
if (!CHdata || CHdata.length == 0) {
CHdata = "User has no Channels";
return rej(CHdata);
}
return res(CHdata);
} catch (error) {
return rej(error);
}
})
}
/** Edit a specific YTChannel in a Guild ID
* @param {string} ChannelLink Youtube Channel Link, example: https://youtube.com/c/Tomato6966
* @param {OBJECT|DiscordChannel} DiscordChannel DiscordChannel with ID && guild parameters
* @param {OBJECT|DiscordUser} DiscordUser DiscordUser with ID parameter. who owns the Link
* @param {string} Notification Notification Message | OPTIONAL | DEFAULT: uses the options
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-editChannel
*/
async editChannel(ChannelLink, DiscordChannel, DiscordUser, Notification = this.options.defaults.Notification) {
return new Promise(async (res, rej) => {
try {
if (!ChannelLink) return rej("A String is required for the ChannelLink");
if (typeof ChannelLink !== "string") return rej(`Passed in ${typeof ChannelLink} but a String would be required for the ChannelLink`);
if (!Util.isValidURL(ChannelLink)) return rej(`${ChannelLink} is not a Valid URL (YT)`);
if (!DiscordChannel || !DiscordChannel.guild || !DiscordChannel.id) return rej("A DiscordChannel with Guild Information is required!");
if(!this.YTP_DB.has(DiscordChannel.guild.id)){
this.YTP_DB.ensure(DiscordChannel.guild.id, {
channels: []
});
}
let channels = this.YTP_DB.get(`${DiscordChannel.guild.id}`, `channels`);
let CHdata = channels.find(v => v.YTchannel.split("/")[v.YTchannel.split("/").length - 1] == ChannelLink.split("/")[ChannelLink.split("/").length - 1])
let index = channels.findIndex(v => v.YTchannel.split("/")[v.YTchannel.split("/").length - 1] == ChannelLink.split("/")[ChannelLink.split("/").length - 1])
if (!CHdata) {
rej("Channel not setup yet")
return;
}
let newCHdata = {
YTchannel: ChannelLink,
DiscordGuild: DiscordChannel.guild.id,
DiscordChannel: DiscordChannel.id,
DiscordUser: DiscordUser ? DiscordUser.id : "",
oldvid: CHdata.oldvid,
alrsent: CHdata.alrsent,
message: Notification
}
//remove item from the channels array which we got
channels[index] = newCHdata;
//set the new channels
this.YTP_DB.set(`${DiscordChannel.guild.id}`, channels, `channels`);
let data = this.YTP_DB.get(`${DiscordChannel.guild.id}`, `channels`);
var Obj = {};
Obj = newCHdata;
Obj.allChannels = data;
Obj.beforeEditChannel = CHdata;
return res(Obj);
} catch (error) {
return rej(error);
}
})
}
/** Delete a specific YTChannel in a Guild
* @param {string} DiscordGuildID Discord Guild id
* @param {string} ChannelLink Youtube Channel Link, example: https://youtube.com/c/Tomato6966
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-deleteChannel
*/
async deleteChannel(DiscordGuildID, ChannelLink) {
return new Promise(async (res, rej) => {
try {
if (!ChannelLink) return rej("A String is required for the ChannelLink");
if (typeof ChannelLink !== "string") return rej(`Passed in ${typeof ChannelLink} but a String would be required for the ChannelLink`);
if (!DiscordGuildID) return rej("A String is required for the DiscordGuildID");
if (typeof DiscordGuildID !== "string" || DiscordGuildID.length != 18) return rej(`Passed in ${typeof DiscordGuildID} but a String would be required for the DiscordGuildID`);
if (!Util.isValidURL(ChannelLink)) return rej(`${ChannelLink} is not a Valid URL (YT)`);
if(!this.YTP_DB.has(DiscordGuildID)){
this.YTP_DB.ensure(DiscordGuildID, {
channels: []
});
}
let channels = this.YTP_DB.get(`${DiscordGuildID}`, `channels`);
let CHdata = channels.find(v => v.YTchannel.split("/")[v.YTchannel.split("/").length - 1] == ChannelLink.split("/")[ChannelLink.split("/").length - 1])
let index = channels.findIndex(v => v.YTchannel.split("/")[v.YTchannel.split("/").length - 1] == ChannelLink.split("/")[ChannelLink.split("/").length - 1])
if (!CHdata) {
rej("Channel not setup yet")
return;
}
//remove item from the channels array which we got
channels.splice(index, 1);
//set the new channels
this.YTP_DB.set(`${DiscordGuildID}`, channels, `.channels`);
let data = this.YTP_DB.get(`${DiscordGuildID}`, `channels`);
var Obj = {};
Obj.allChannels = data;
Obj.deletedChannel = CHdata;
return res(Obj);
} catch (error) {
return rej(error);
}
})
}
/** Gets all Channels of a Guild
* @param {string} DiscordGuildID Discord Guild id
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-getAllChannels
*/
async getAllChannels(DiscordGuildID) {
return new Promise(async (res, rej) => {
try {
if (!DiscordGuildID) return rej("A String is required for the DiscordGuildID");
if (typeof DiscordGuildID !== "string" || DiscordGuildID.length != 18) return rej(`Passed in ${typeof DiscordGuildID} but a String would be required for the DiscordGuildID`);
if(!this.YTP_DB.has(DiscordGuildID)){
this.YTP_DB.ensure(DiscordGuildID, {
channels: []
});
}
let channels = this.YTP_DB.get(`${DiscordGuildID}`, `channels`);
return res(channels);
} catch (error) {
return rej(error);
}
})
}
/** Delete all Channels in a GUild
* @param {string} DiscordGuildID Discord Guild id
* returns: https://github.com/Tomato6966/discord-yt-poster/wiki/Response-deleteAllChannels
*/
async deleteAllChannels(DiscordGuildID) {
return new Promise(async (res, rej) => {
try {
if (!DiscordGuildID) return rej("A String is required for the DiscordGuildID");
if (typeof DiscordGuildID !== "string" || DiscordGuildID.length != 18) return rej(`Passed in ${typeof DiscordGuildID} but a String would be required for the DiscordGuildID`);
let olddata = this.YTP_DB.get(`${DiscordGuildID}`, `channels`);
this.YTP_DB.set(DiscordGuildID, {
channels: []
});
let data = this.YTP_DB.get(`${DiscordGuildID}`, `channels`);
const Obj = {};
Obj.allChannels = data;
Obj.deletedChannels = olddata;
return res(Obj);
} catch (error) {
return rej(error);
}
})
}
}
module.exports = YoutubePoster;