Added YouTube RSS feed monitoring

This commit is contained in:
Ghoti 2025-08-20 23:06:58 -05:00
parent 6a77ea0c06
commit 8d2648a895
10 changed files with 550 additions and 4 deletions

View File

@ -151,5 +151,10 @@
<artifactId>pf4j</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>xom</groupId>
<artifactId>xom</artifactId>
<version>1.3.9</version>
</dependency>
</dependencies>
</project>

View File

@ -14,6 +14,7 @@ import com.fpghoti.biscuit.commands.console.GuildSayCommand;
import com.fpghoti.biscuit.commands.console.SayCommand;
import com.fpghoti.biscuit.commands.console.ShutdownConsoleCommand;
import com.fpghoti.biscuit.commands.discord.AddCommand;
import com.fpghoti.biscuit.commands.discord.AddYTFeedCommand;
import com.fpghoti.biscuit.commands.discord.ChanIDCommand;
import com.fpghoti.biscuit.commands.discord.DivideCommand;
import com.fpghoti.biscuit.commands.discord.GetConfigCommand;
@ -100,6 +101,7 @@ public class Main {
private static Cage cage;
private static File pluginsDir;
public static File audioDir;
//public static File ytFeedDir;
private static PluginController pluginController;
private static AudioPlayerManager playerManager;
@ -131,9 +133,7 @@ public class Main {
playerManager = new DefaultAudioPlayerManager();
AudioSourceManagers.registerLocalSource(playerManager);
YoutubeAudioSourceManager ytSourceManager = new dev.lavalink.youtube.YoutubeAudioSourceManager();
//LocalAudioSourceManager localSourceManager = new LocalAudioSourceManager();
playerManager.registerSourceManager(ytSourceManager);
//playerManager.registerSourceManager(localSourceManager);
AudioSourceManagers.registerRemoteSources(playerManager,
com.sedmelluq.discord.lavaplayer.source.youtube.YoutubeAudioSourceManager.class);
@ -175,6 +175,7 @@ public class Main {
CommandManager.addCommand(new ToggleRoleCommand());
CommandManager.addCommand(new SquareRootCommand());
CommandManager.addCommand(new AddCommand());
CommandManager.addCommand(new AddYTFeedCommand());
CommandManager.addCommand(new SubtractCommand());
CommandManager.addCommand(new MultiplyCommand());
CommandManager.addCommand(new DivideCommand());

View File

@ -0,0 +1,52 @@
package com.fpghoti.biscuit.commands.discord;
import com.fpghoti.biscuit.Main;
import com.fpghoti.biscuit.commands.base.ClientCommand;
import com.fpghoti.biscuit.guild.BiscuitGuild;
import com.fpghoti.biscuit.rest.MessageText;
import com.fpghoti.biscuit.util.PermUtil;
import com.fpghoti.biscuit.util.Util;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
//import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
public class AddYTFeedCommand extends ClientCommand{
public AddYTFeedCommand() {
name = "Add YouTube Feed";
description = "Sets bot to post YouTube uploads for a specified YouTube channel RSS feed inside the text channel the command was run in";
usage = Main.getMainBiscuit().getProperties().getCommandSignifier() + "addytfeed <alias> <channel-rss-url> <message>";
minArgs = 1;
maxArgs = 1000000;
identifiers.add("addytfeed");
}
@Override
public void execute(String[] args, MessageReceivedEvent event) {
System.out.println("0");
BiscuitGuild b = BiscuitGuild.getBiscuitGuild(event.getGuild());
b.log(event.getAuthor().getName() + " issued a command: -addytfeed " + args[0]);
System.out.println("A");
if((PermUtil.isAdmin(event.getMember()))) {
System.out.println("B");
Guild g = event.getGuild();
TextChannel c = event.getChannel().asTextChannel();
String alias = args[0];
String channelURL = args[1];
String message = Util.getArgsMessage(2, args);
System.out.println("C");
boolean success = b.addYoutubeFeed(alias, c, channelURL, message);
System.out.println("D");
if(success) {
MessageText.send(event.getChannel().asTextChannel(), "YouTube Feed has been created.");
}else {
MessageText.send(event.getChannel().asTextChannel(), "There was an error creating the YouTube feed.");
}
}else {
MessageText.send(event.getChannel().asTextChannel(), "You do not have permission to create a YouTube feed.");
}
}
}

View File

@ -152,7 +152,7 @@ public class BiscuitConfig {
//Add properties to appear in both types of configs here
added = addProperty("ChatLog", "true", prop, added, silent);
added = addProperty("AllowSpamPunish", "true", prop, added, silent);
added = addProperty("AllowSpamPunish", "false", prop, added, silent);
added = addProperty("Channels-To-Not-Chatlog", "ignore_me,ignore_me2,ignore_me3", prop, added, silent);
added = addProperty("NaughtyList", "piff,word123,another1", prop, added, silent);
added = addProperty("Restrict-Cmd-Channels", "false", prop, added, silent);

View File

@ -1,11 +1,18 @@
package com.fpghoti.biscuit.guild;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.Timer;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.fpghoti.biscuit.Main;
import com.fpghoti.biscuit.PluginCore;
@ -15,6 +22,8 @@ import com.fpghoti.biscuit.config.BiscuitProperties;
import com.fpghoti.biscuit.logging.BColor;
import com.fpghoti.biscuit.logging.BiscuitLogger;
import com.fpghoti.biscuit.rest.MessageText;
import com.fpghoti.biscuit.rss.YTFeed;
import com.fpghoti.biscuit.rss.YTFeedConfig;
import com.fpghoti.biscuit.timer.BiscuitTimer;
import com.fpghoti.biscuit.timer.task.ChatCountTimer;
import com.fpghoti.biscuit.timer.task.DecrementTimer;
@ -29,7 +38,6 @@ import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Invite;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.Role;
//import net.dv8tion.jda.api.entities.TextChannel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.User;
@ -83,9 +91,11 @@ public class BiscuitGuild {
private Timer timer;
private List<BiscuitTimer> timers;
private File captchaDir;
private File ytFeedDir;
//private Cage cage;
private Guild guild;
private HashMap<String, Integer> inviteUses;
private HashMap<String, YTFeedConfig> ytfeeds;
private BiscuitConfig config;
private BiscuitProperties properties;
private GuildMessageStore messageStore;
@ -106,6 +116,7 @@ public class BiscuitGuild {
this.properties = new BiscuitProperties(this);
this.rolequeue = new HashMap<Member, Role>();
this.player = Main.getPlayerManager().createPlayer();
this.ytfeeds = new HashMap<String, YTFeedConfig>();
scheduler = new AudioScheduler(this);
player.addListener(scheduler);
@ -115,9 +126,19 @@ public class BiscuitGuild {
if(!Main.isPlugin) {
captchaDir = new File("captcha");
captchaDir.mkdir();
if(guild != null) {
File f = new File("ytchannels");
f.mkdir();
ytFeedDir = new File(f, guild.getId());
ytFeedDir.mkdir();
}
}else {
captchaDir = new File(PluginCore.plugin.getDataFolder(), "captcha");
captchaDir.mkdir();
if(guild != null) {
ytFeedDir = new File(PluginCore.plugin.getDataFolder(), "ytchannels\\" + guild.getId());
ytFeedDir.mkdirs();
}
}
if(isMain) {
wipeCaptchaDir();
@ -131,6 +152,20 @@ public class BiscuitGuild {
});
}
}
if(guild != null) {
loadYoutubeFeeds();
Runnable post = () -> {
try {
log("Updating Youtube feeds...");
postYoutubeFeeds();
} catch (IOException e) {
e.printStackTrace();
}
};
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(post, 0, 1, TimeUnit.MINUTES);
}
}
public BiscuitConfig getConfig() {
@ -161,6 +196,87 @@ public class BiscuitGuild {
return scheduler;
}
public YTFeedConfig loadYouTubeFeedConfig(String alias) {
alias = alias.toLowerCase();
String channelID = "null";
String youTubeChannelURL = "null";
String message = "null";
String lastVideo = "null";
Properties prop = new Properties();
InputStream input = null;
File config = new File(ytFeedDir, alias);
if(!config.exists()) {
logger.error("Could not locate YouTube feed config.");
return null;
}
try {
input = new FileInputStream(config);
prop.load(input);
channelID = prop.getProperty("TextChannelID");
youTubeChannelURL = prop.getProperty("YouTubeChannelURL");
message = prop.getProperty("Message");
lastVideo = prop.getProperty("LastVideo");
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
TextChannel textChannel = jda.getTextChannelById(channelID);
if(textChannel == null) {
logger.error("Error retrieving Text Channel from YouTube feed file.");
return null;
}
YTFeed feed = new YTFeed(alias, textChannel, youTubeChannelURL, message);
feed.setLastVideo(lastVideo);
return new YTFeedConfig(this, feed);
}
public void loadYoutubeFeeds() {
ytfeeds.clear();
File[] files = ytFeedDir.listFiles();
for(File f : files) {
String alias = f.getName().toLowerCase();
ytfeeds.put(alias, loadYouTubeFeedConfig(alias));
}
}
public boolean addYoutubeFeed(String alias, TextChannel channel, String channelURL, String message) {
alias = alias.toLowerCase();
if(guild == null || ytfeeds.containsKey(alias)) {
return false;
}
YTFeed feed = new YTFeed(alias, channel, channelURL, message);
//Generate a file for the feed
new YTFeedConfig(this, feed);
//Loads all feeds from files into hash map
loadYoutubeFeeds();
return true;
}
public void postYoutubeFeeds() throws IOException {
for(String s : ytfeeds.keySet()) {
YTFeedConfig config = ytfeeds.get(s);
YTFeed feed = config.getFeed();
feed.post();
config.setLastPosted(feed.getLastVideo());
}
}
public File getYTFeedDir() {
return ytFeedDir;
}
public void log(String message) {
if(properties == null) {
logger.info(message);

View File

@ -0,0 +1,67 @@
package com.fpghoti.biscuit.rss;
import java.awt.Color;
import com.fpghoti.biscuit.util.Util;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.MessageEmbed;
public class YTEntry {
private String id;
private String title;
private String author;
private String description;
private String thumbnail;
public YTEntry(String id, String title, String author, String description, String thumbnail) {
this.id = id;
this.title = title;
this.author = author;
this.description = description;
this.thumbnail = thumbnail;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public String getDescription() {
return description;
}
public String getThumbnail() {
return thumbnail;
}
public String getURL() {
return "https://www.youtube.com/watch?v=" + id;
}
public MessageEmbed getEmbedMessage() {
EmbedBuilder embed = new EmbedBuilder();
embed.setTitle(title, getURL());
embed.setColor(Color.RED);
String descPreview = description.substring(0, Math.min(description.length(), 200));
if(descPreview.length() < description.length()) {
descPreview += "...";
}
embed.setDescription(descPreview);
embed.setAuthor(author, null, null);
embed.setThumbnail(thumbnail);
return embed.build();
}
}

View File

@ -0,0 +1,143 @@
package com.fpghoti.biscuit.rss;
import java.awt.Color;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.fpghoti.biscuit.guild.BiscuitGuild;
import com.fpghoti.biscuit.rest.MessageText;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.ParsingException;
public class YTFeed {
private BiscuitGuild guild;
private String alias;
private TextChannel channel;
private String channelURL;
private String message;
private String lastVideo;
public YTFeed(String alias, TextChannel channel, String channelURL, String message) {
this.guild = BiscuitGuild.getBiscuitGuild(channel.getGuild());
this.alias = alias;
this.channel = channel;
this.channelURL = channelURL;
this.message = message;
lastVideo = "";
}
public String getAlias() {
return alias;
}
public TextChannel getTextChannel() {
return channel;
}
public String getChannelURL() {
return channelURL;
}
public String getMesage() {
return message;
}
public String getLastVideo() {
return lastVideo;
}
public void setLastVideo(String link) {
lastVideo = link;
}
public void post(){
List<YTEntry> ytentries = getEntries();
int index = 0;
int lastVidIndex = -1;
for(YTEntry entry : ytentries) {
String link = entry.getURL();
if(link.equals(lastVideo)) {
lastVidIndex = index;
}
index++;
}
index = 0;
for(YTEntry entry : ytentries) {
if(index > lastVidIndex) {
String link = entry.getURL();
lastVideo = link;
MessageText.send(channel, message);
MessageText.send(channel, entry.getEmbedMessage());
}
index++;
}
}
public List<YTEntry> getEntries() {
List<YTEntry> ytentries = new ArrayList<YTEntry>();
Builder builder = new Builder();
Document doc = null;
try {
doc = builder.build(channelURL);
} catch (ParsingException | IOException e) {
guild.error("Unable to parse feed: " + channelURL);
MessageText.send(channel, "Unable to parse feed: " + channelURL);
}
Element rss = doc.getRootElement();
Elements entries = rss.getChildElements();
for(Element entry : entries){
if(entry.getLocalName().equals("entry")) {
String id = "";
String title = "";
String author = "";
String description = "";
String thumbnail = "";
Elements values = entry.getChildElements();
for(Element value : values){
if(value.getLocalName().equals("videoId")) {
id = value.getValue();
}
if(value.getLocalName().equals("title")) {
title = value.getValue();
}
if(value.getLocalName().equals("author")) {
Elements subvalues = value.getChildElements();
for(Element subvalue : subvalues) {
if(subvalue.getLocalName().equals("name")) {
author = subvalue.getValue();
}
}
}
if(value.getLocalName().equals("group")) {
Elements subvalues = value.getChildElements();
for(Element subvalue : subvalues) {
if(subvalue.getLocalName().equals("description")) {
description = subvalue.getValue();
}
if(subvalue.getLocalName().equals("thumbnail")) {
thumbnail = subvalue.getAttributeValue("url");
}
}
}
}
ytentries.add(new YTEntry(id, title, author, description, thumbnail));
}
}
return ytentries.reversed();
}
}

View File

@ -0,0 +1,149 @@
package com.fpghoti.biscuit.rss;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.PropertiesConfigurationLayout;
import org.apache.commons.configuration2.ex.ConfigurationException;
import com.fpghoti.biscuit.guild.BiscuitGuild;
public class YTFeedConfig {
private BiscuitGuild biscuit;
private YTFeed feed;
private File config;
public YTFeedConfig(BiscuitGuild b, YTFeed feed) {
this.biscuit = b;
this.feed = feed;
generateConfig();
}
public BiscuitGuild getGuild() {
return biscuit;
}
public YTFeed getFeed() {
return feed;
}
public void generateConfig() {
String name = feed.getAlias();
File config = new File(biscuit.getYTFeedDir(), name);
if (!config.exists()) {
try {
config.createNewFile();
updateConfig(config);
} catch (Exception e) {
e.printStackTrace();
}
}else {
updateConfig(config);
}
this.config = config;
}
public void setLastPosted(String link) {
PropertiesConfiguration prop = new PropertiesConfiguration();
PropertiesConfigurationLayout layout = new PropertiesConfigurationLayout();
prop.setLayout(layout);
try {
layout.load(prop, new FileReader(config));
FileWriter fw = new FileWriter(config);
prop.setProperty("LastVideo", link);
feed.setLastVideo(link);
layout.save(prop, fw);
} catch (ConfigurationException e) {
e.printStackTrace();
biscuit.error("There was an issue updating a YouTube feed file.");
return;
} catch (IOException e) {
e.printStackTrace();
biscuit.error("There was an issue updating a YouTube feed file.");
return;
}
}
private void updateConfig(File config) {
PropertiesConfiguration prop = new PropertiesConfiguration();
PropertiesConfigurationLayout layout = new PropertiesConfigurationLayout();
prop.setLayout(layout);
try {
layout.load(prop, new FileReader(config));
FileWriter fw = new FileWriter(config);
addNewOptions(prop);
layout.save(prop, fw);
} catch (ConfigurationException e) {
e.printStackTrace();
biscuit.error("There was an issue preparing a YouTube feed config for updates.");
return;
} catch (IOException e) {
e.printStackTrace();
biscuit.error("There was an issue preparing a YouTube feed config for updates.");
return;
}
}
private void addNewOptions(PropertiesConfiguration prop) {
if(feed == null) {
return;
}
addProperty("Alias", feed.getAlias(), prop);
addProperty("TextChannelID", feed.getTextChannel().getId(), prop);
addProperty("YouTubeChannelURL", feed.getChannelURL(), prop);
addProperty("Message", feed.getMesage(), prop);
addProperty("LastVideo", "", prop);
}
private void addProperty(String key, String value, PropertiesConfiguration prop) {
if(prop.getProperty(key) == null) {
prop.addProperty(key, value);
}
}
public String getFromConfig(String property){
String setting = "";
Properties prop = new Properties();
InputStream input = null;
File config = new File(biscuit.getYTFeedDir(), feed.getAlias());
if(!config.exists()) {
return "";
}
try {
input = new FileInputStream(config);
prop.load(input);
setting = prop.getProperty(property);
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return setting;
}
}

View File

@ -84,4 +84,15 @@ public class Util {
return last;
}
public static String getArgsMessage(int startAt, String[] args) {
String message = "";
for(int i = startAt; i < args.length; i++) {
message = message + args[i] + " ";
}
if (message.length() > 0) {
message = message.substring(0, message.length() - 1);
}
return message;
}
}

View File

@ -38,4 +38,6 @@
<appender-ref ref="FILE-AUDIT" />
</logger>
<logger name="dev.lavalink.youtube.http.YoutubeOauth2Handler" level="INFO" />
</configuration>