From d26096eea2d8843450119f7fa94eed60a59730e0 Mon Sep 17 00:00:00 2001 From: Cristiano Pires Date: Sun, 5 Mar 2023 17:04:55 +0000 Subject: [PATCH] Added youtube feeds! --- commands/admin/setFeedId.js | 112 ++++++++++++++++++++++++++++++++++++ lib.js | 82 +++++++++++++++++++++++++- models/feeds.js | 17 ++++++ models/guilds.js | 6 +- package-lock.json | 20 +++++++ package.json | 1 + test.js | 93 +++++++++++++++++------------- 7 files changed, 285 insertions(+), 46 deletions(-) create mode 100644 commands/admin/setFeedId.js create mode 100644 models/feeds.js diff --git a/commands/admin/setFeedId.js b/commands/admin/setFeedId.js new file mode 100644 index 0000000..598e759 --- /dev/null +++ b/commands/admin/setFeedId.js @@ -0,0 +1,112 @@ +const {Command, Random, ErrorMessage, ErrorType} = require('../../lib.js') +const {EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle} = require('discord.js'); +const xmlparser = require('xml-js') +const feedM = require('../../models/feeds'); +class setYTFeed extends Command{ + constructor(client){ + super(client, { + name: 'setFeed', + aliases:['setYTFeed','setytf', 'setYTF'], + description: 'Sets the message channel where a defined youtube channel feed will be displayed. (Order: YTchannelId, discord channel id, Costum message to display when a new video is released.) You can only assign one feed per channel.', + needsAdmin:true, + }); + this.client = client; + } + async run(message, args) + { + + // Validate the command arguments + const validationResult = await this.confirmArgs(message, args); + if (validationResult.error) return new ErrorMessage(this.client).send(ErrorType.Arguments, message, [validationResult.error]) + var feed = new feedM(); + feed.ChannelId = validationResult.channelId + feed.YTChannelId = validationResult.YTChannelId; + feed.CostumMessage = args[3]?args.splice(2).join(' '):'New video:'; + feed.save(err=> + { + if(err)console.log(err); + }) + const embed = new EmbedBuilder() + const randomID = Random(); + embed.setFooter({text:'Rem-chan on ', iconURL:"https://i.imgur.com/g6FSNhL.png"}) + embed.setAuthor({name:"Rem-chan", iconURL:"https://i.imgur.com/g6FSNhL.png",url:'https://rem.wordfights.com/addtodiscord'}); + embed.setColor(0x110809); + embed.setTimestamp() + embed.setTitle(`${validationResult.name}'s feed defined on channel ${validationResult.channelName}.`) + if(args!='New video:') embed.setDescription(`With the costum message: \n ${feed.CostumMessage}`) + + const row = new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId(randomID) + .setLabel('Remove this.') + .setStyle(ButtonStyle.Primary), + ); + const filter = i => i.customId === randomID; + message.channel.send({embeds: [embed], components: [row] }); + const collector = message.channel.createMessageComponentCollector({ filter, time: 60000 }); + collector.on('collect', async m => + { + _delete(message, m); + }); + collector.on('end', async m=> + { + _delete(message,m); + }) + } + + + /** + * + * @param {Map} message + * @param {Array} args + * @returns {RoleModel} + * + */ + async confirmArgs(message,args) + { + if(args.length<2) return {error:'Unsuficient amount of arguments.'} + var YTChannelId = args[0]; + var channelId = args[1]; + const exists = await this.client.channels.fetch(channelId).then(channel=>{return channel }).catch(()=>{return }); + if(!exists) return {error:'Provided channel id does not exist.'} + + var name = await fetch(`https://www.youtube.com/feeds/videos.xml?channel_id=${YTChannelId}`) + .then(handleResponse) + .then(handleData) + .catch(handleError); + function handleResponse(response) + { + return response.text() + } + function handleError(error) + { + return error; + } + function handleData(data) + { + data = xmlparser.xml2json(data, + { + compact: true, + space: 4 + }); + data = JSON.parse(data); + return data.feed.author.name._text + } + if(!name) return {error:'Provided Youtube channel id does not exist.'} + const channelInUse = await feedM.findOne({ChannelId:channelId}).then(channel=>{return channel}).catch(()=>{return []}); + if(channelInUse) return {error:'Discord channel already in use. Please choose another.'} + return {YTChannelId, channelId, name, channelName:exists.name} + } + +}module.exports = setYTFeed; + +function _delete(message, m) +{ + try { + message.delete(); + m.delete(); + } catch (error) { + console.log('SetYTID: Error deleting the message:',error) + } +} \ No newline at end of file diff --git a/lib.js b/lib.js index 5d11849..9aed5a2 100644 --- a/lib.js +++ b/lib.js @@ -2,6 +2,8 @@ const path = require('path'); const find = require('findit'); const {Client,ClientOptions,GatewayIntentBits,Message, Partials, ActivityType,EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle} = require('discord.js') const roleRulesM = require('./models/autoRoleRule'); +const feedM = require('./models/feeds'); +const xmlparser = require('xml-js') module.exports.GatewayIntentBits = GatewayIntentBits; module.exports.Partials = Partials; module.exports.ActivityType = ActivityType; @@ -103,7 +105,10 @@ class _Client extends Client // Log a message to the console console.log('Client is reconnecting...'); }); - + this.YTFeed(); + setInterval(() => { + this.YTFeed() + }, 60*60*1000); } /** * Register a group of commands under the folder commands @@ -163,6 +168,81 @@ class _Client extends Client })() }) } + async YTFeed() + { + const feeds = await feedM.find().then(feeds=>{return feeds}) + if(feeds.length<1) return + for(var feed of feeds) + { + (async (feed)=>{ + var res = await fetch(`https://www.youtube.com/feeds/videos.xml?channel_id=${feed.YTChannelId}`) + .then(handleResponse) + .then(handleData) + .catch(handleError); + function handleResponse(response) + { + return response.text() + } + function handleError(error) + { + return error; + } + function handleData(data) + { + data = xmlparser.xml2json(data, + { + compact: true, + space: 4 + }); + data = JSON.parse(data); + return { + link:data.feed.entry[0].link._attributes.href, + published:data.feed.entry[0].published._text, + link:data.feed.entry[0].link._attributes.href, + image:data.feed.entry[0].link._attributes.href.split('=')[1], + title:data.feed.entry[0].title._text, + author:{ + name:data.feed.entry[0].author.name._text, + url:data.feed.entry[0].author.uri._text + } + } + } + const channel = this.channels.cache.get(feed.ChannelId) + const lastSentMessage = await channel.messages.fetch().then(res=> + { + for(var item of res) + { + if(item[1].embeds) + { + if(item[1].embeds[0]) + { + var fields = item[1].embeds[0].fields.filter(x=>x.name === 'PublishedTimeStamp'); + var isFeed = fields.length>0; + if(isFeed) return fields[0].value + } + } + } + }) + if(!lastSentMessage) sendMessage(res, feed, channel) + else if(lastSentMessage != res.published) sendMessage(res, feed, channel); + })(feed); + } + function sendMessage(res, feed, channel) + { + const embed = new EmbedBuilder() + embed.setFooter({text:'Rem-chan on ', iconURL:"https://i.imgur.com/g6FSNhL.png"}) + embed.setAuthor({name:"Rem-chan", iconURL:"https://i.imgur.com/g6FSNhL.png",url:'https://rem.wordfights.com/addtodiscord'}); + embed.setColor(0x110809); + embed.setTimestamp(); + embed.setURL(res.author.url) + embed.setTitle(res.author.name); + embed.setImage(`https://i3.ytimg.com/vi/${res.image}/maxresdefault.jpg`) + embed.setDescription(feed.CostumMessage); + embed.addFields({name:'PublishedTimeStamp', value:res.published}, + {name:'Link:', value:res.link}); + channel.send({embeds:[embed]}); + } + } }module.exports.Client = _Client; const ErrorType = diff --git a/models/feeds.js b/models/feeds.js new file mode 100644 index 0000000..d5475ab --- /dev/null +++ b/models/feeds.js @@ -0,0 +1,17 @@ +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +let feed = +new Schema( + { + ChannelId: {type:String, required: true}, + YTChannelId: {type:String, required: true}, + CostumMessage:{type:String, required:false}, + } +); + +const Feed = module.exports = mongoose.model('feed', feed); +module.exports.get = (callback, limit)=> +{ + Feed.find(callback).limit(limit); +} \ No newline at end of file diff --git a/models/guilds.js b/models/guilds.js index c1ec2a7..55162a1 100644 --- a/models/guilds.js +++ b/models/guilds.js @@ -8,13 +8,11 @@ new Schema( memberCount: {type:Number, required: false}, gID:{type:String, required:true, unique:true}, strikes:{type:Boolean}, - music:{type:Boolean} + music:{type:Boolean}, + ytFeeds:{type:Boolean} } ); - - - const Guild = module.exports = mongoose.model('guild', guild); module.exports.get = (callback, limit)=> { diff --git a/package-lock.json b/package-lock.json index cc6b76c..c2bffb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "socket.io": "^4.5.3", "tweetnacl": "^1.0.3", "utf-8-validate": "^5.0.10", + "xml-js": "^1.6.11", "yt-search": "^2.10.3", "ytdl-core": "^4.11.2", "zlib-sync": "^0.1.7" @@ -3844,6 +3845,17 @@ } } }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, "node_modules/yt-search": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/yt-search/-/yt-search-2.10.3.tgz", @@ -6825,6 +6837,14 @@ "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "requires": {} }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "yt-search": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/yt-search/-/yt-search-2.10.3.tgz", diff --git a/package.json b/package.json index 71bbcc5..d9de517 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "socket.io": "^4.5.3", "tweetnacl": "^1.0.3", "utf-8-validate": "^5.0.10", + "xml-js": "^1.6.11", "yt-search": "^2.10.3", "ytdl-core": "^4.11.2", "zlib-sync": "^0.1.7" diff --git a/test.js b/test.js index cb8aeed..9bd9266 100644 --- a/test.js +++ b/test.js @@ -1,42 +1,53 @@ -const {Client, GatewayIntentBits,Partials, AtivityType} = require('./lib.js'); -const mongoose = require('mongoose'); -const mongoDB = process.env.mongoDB; - -const bot = new Client( - { - intents: - [ - GatewayIntentBits.Guilds, - GatewayIntentBits.GuildMembers, - GatewayIntentBits.GuildBans, - GatewayIntentBits.GuildEmojisAndStickers, - GatewayIntentBits.GuildIntegrations , - GatewayIntentBits.GuildWebhooks , - GatewayIntentBits.GuildInvites , - GatewayIntentBits.GuildVoiceStates, - GatewayIntentBits.GuildPresences, - GatewayIntentBits.GuildMessages, - GatewayIntentBits.GuildMessageReactions, - GatewayIntentBits.GuildMessageTyping, - GatewayIntentBits.DirectMessages, - GatewayIntentBits.DirectMessageReactions, - GatewayIntentBits.DirectMessageTyping, - GatewayIntentBits.MessageContent, - GatewayIntentBits.GuildScheduledEvents, - GatewayIntentBits.AutoModerationConfiguration, - GatewayIntentBits.AutoModerationExecution - ], - partials: - [ - Partials.Reaction, - Partials.Message - ] - } -) -bot.login(process.env.discord_token); -bot.on('ready', ()=> +var channelId = 'UCRsrOaKEdy0ymNqR7Urqa2Q' +const xmlparser = require('xml-js'); +(async function () { - mongoose.Promise = global.Promise; - mongoose.connect(mongoDB).catch(err=>console.log(err)); - console.log('Ready'); -}); \ No newline at end of file + var coiso = await fetch(`https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`) + .then(handleResponse) + .then(handleData) + .catch(handleError); + function handleResponse(response) + { + return response.text() + } + function handleError(error) + { + return error; + } + function handleData(data) + { + data = xmlparser.xml2json(data, + { + compact: true, + space: 4 + }); + data = JSON.parse(data) + // console.log({ + // channelName:data.feed.author.name._text, + // lasVid: + // { + // link:data.feed.entry[0].link._attributes.href, + // published:data.feed.entry[0].published._text + // } + // }) + console.log(data.feed.entry[0]) + + return { + channelName:data.feed.author.name._text, + lasVid: + { + link:data.feed.entry[0].link._attributes.href, + published:data.feed.entry[0].published._text, + link:data.feed.entry[0].link._attributes.href, + image:data.feed.entry[0].link._attributes.href.split('=')[1], + title:data.feed.entry[0].title._text, + author:{ + name:data.feed.entry[0].author.name._text, + url:data.feed.entry[0].author.url._text + } + + } + } + } + //console.log(coiso, coiso.channelName) +})() \ No newline at end of file