Skip to content

Commit

Permalink
Add RSS feed registration and management commands (#110)
Browse files Browse the repository at this point in the history
Implement commands to register and delete RSS feeds, along with an RSS parser. Update versioning and remove unnecessary development scripts.
  • Loading branch information
yuito-it authored Dec 7, 2024
2 parents 32b9e75 + 1487258 commit 202c87e
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 22 deletions.
2 changes: 1 addition & 1 deletion argoCD/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ spec:
spec:
containers:
- name: unibot
image: registry.uniproject-tech.net/unibot/unibot:3.2.13
image: registry.uniproject-tech.net/unibot/unibot:3.3.0-1
envFrom:
- secretRef:
name: unibot-env
Expand Down
98 changes: 98 additions & 0 deletions commands/admin/feed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
const { addSubCommand, subCommandHandling } = require("../../lib/commandUtils");
const { GetLogChannel, GetErrorChannel } = require("../../lib/channelUtils");

module.exports = {
guildOnly: true,
adminGuildOnly: true,
handlingCommands: subCommandHandling("admin/feed"),
data: addSubCommand(
"admin/feed",
new SlashCommandBuilder()
.setName("feed")
.setDescription("RSS feed/atom feed Utilities")
),
/*data: new SlashCommandBuilder()
.setName("feed")
.setDescription("Regist RSS feed")
.addStringOption((option) =>
option.setName("url").setDescription("URL of RSS feed").setRequired(true)
),*/
async execute(interaction) {
if (interaction.member.permissions.has("ADMINISTRATOR") === false) {
interaction.reply({
content: "You don't have permission to use this command.",
ephemeral: true,
});
return "No permission";
}
const command = this.handlingCommands.get(
interaction.options.getSubcommand()
);
await interaction.deferReply();
if (!command) {
console.log(`[-] Not Found: ${interaction.options.getSubcommand()}`);
return;
}
try {
await command.execute(interaction);
console.log(`[Run] ${interaction.options.getSubcommand()}`);

const logEmbed = new EmbedBuilder()
.setTitle("サブコマンド実行ログ")
.setDescription(`${interaction.user} がサブコマンドを実行しました。`)
.setColor(interaction.client.conf.color.s)
.setTimestamp()
.setThumbnail(interaction.user.displayAvatarURL({ dynamic: true }))
.addFields([
{
name: "サブコマンド",
value: `\`\`\`\n/${interaction.options.getSubcommand()}\n\`\`\``,
},
{
name: "実行サーバー",
value:
"```\n" + interaction.inGuild()
? `${interaction.guild.name} (${interaction.guild.id})`
: "DM" + "\n```",
},
{
name: "実行ユーザー",
value:
"```\n" +
`${interaction.user.tag}(${interaction.user.id})` +
"\n```",
},
])
.setFooter({ text: `${interaction.id}` });
const channel = await GetLogChannel(interaction);
if (channel) {
channel.send({ embeds: [logEmbed] });
}
} catch (error) {
console.error(error);
const logEmbed = new EmbedBuilder()
.setTitle("ERROR - cmd")
.setDescription("```\n" + error.toString() + "\n```")
.setColor(config.color.e)
.setTimestamp();

const channel = await GetErrorChannel(interaction);
if (channel) {
channel.send({ embeds: [logEmbed] });
}
const messageEmbed = new EmbedBuilder()
.setTitle("すみません、エラーが発生しました...")
.setDescription("```\n" + error + "\n```")
.setColor(interaction.conf.color.e)
.setTimestamp();

await interaction.editReply(messageEmbed);
const logChannel = await GetLogChannel(interaction);
if (logChannel) {
logChannel.send({ embeds: [messageEmbed] });
}
}
return;
},
};
66 changes: 66 additions & 0 deletions commands/admin/feed/delete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const {
SlashCommandSubcommandBuilder,
StringSelectMenuBuilder,
StringSelectMenuOptionBuilder,
ActionRowBuilder,
ComponentType,
} = require("discord.js");
const logUtils = require("../../../lib/logUtils.js");

module.exports = {
data: new SlashCommandSubcommandBuilder()
.setName("delete")
.setDescription("delete"),
adminGuildOnly: true,
/**
* Executes the feed command.
* @param {CommandInteraction} interaction - The interaction object.
* @returns {Promise<string>} - A promise that resolves when the execution is complete.
* @async
*/
async execute(interaction) {
const subscribed = await logUtils.readLog(
"v1/feed/" + interaction.channel.id
);
if (!subscribed) {
interaction.editReply({
content: "This channel is not subscribed to any feeds.",
ephemeral: true,
});
return "No data";
}
const select = new StringSelectMenuBuilder().setCustomId("FeedSelector");
for (const feed of subscribed.data) {
await select.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel(feed.url)
.setValue(feed.url)
.setDescription("Last Update: " + feed.lastDate)
);
}

const row = new ActionRowBuilder().addComponents(select);

const response = await interaction.editReply({
content: "Choose your starter!",
components: [row],
});

const collector = response.createMessageComponentCollector({
componentType: ComponentType.StringSelect,
time: 3_600_000,
});

collector.on("collect", async (i) => {
const selection = i.values[0];
const index = subscribed.data.findIndex((x) => x.url === selection);
subscribed.data.splice(index, 1);
await logUtils.loging(subscribed, `v1/feed/${interaction.channel.id}`);
await i.update({
content: "Deleted: " + selection,
components: [],
});
});
return "No data";
},
};
66 changes: 66 additions & 0 deletions commands/admin/feed/regist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const { SlashCommandSubcommandBuilder, EmbedBuilder } = require("discord.js");
const Discord = require("discord.js");
const { rssGet } = require("../../../lib/rss.cjs");
const logUtils = require("../../../lib/logUtils.js");

module.exports = {
data: new SlashCommandSubcommandBuilder()
.setName("regist")
.setDescription("Regist RSS feed")
.addStringOption((option) =>
option.setName("url").setDescription("URL of RSS feed").setRequired(true)
),
adminGuildOnly: true,
/**
* Executes the feed command.
* @param {CommandInteraction} i - The interaction object.
* @returns {Promise<string>} - A promise that resolves when the execution is complete.
* @async
*/
async execute(i) {
const feed = await rssGet(i.options.getString("url"));
try {
let log = await logUtils.readLog("v1/feed/" + i.channel.id);
console.log(log);
if (log) {
log.data.push({
url: i.options.getString("url"),
lastDate: feed[0].pubDate,
});
await logUtils.loging(log, `v1/feed/${i.channel.id}`);
} else {
log = {
data: [
{ url: i.options.getString("url"), lastDate: feed[0].pubDate },
],
};
await logUtils.loging(log, `v1/feed/${i.channel.id}`);
}
} catch (e) {
console.error(e);
}
const embed = new Discord.EmbedBuilder()
.setTitle("Registed RSS feed")
.addFields([
{
name: "URL",
value: ` ** ${i.options.getString("url")} ** `,
inline: true,
},
])
.setFields({
name: "Title",
value: ` ** ${feed[0].title} ** `,
inline: true,
})
.setFields({
name: "FirstContent",
value: ` ** ${feed[0].content} ** `,
inline: true,
})
.setColor(i.client.conf.color.s)
.setTimestamp();
i.editReply({ embeds: [embed] });
return "No data";
},
};
2 changes: 1 addition & 1 deletion config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(require('dotenv')).config();
//(require('dotenv')).config();
module.exports = {
adminRoleId: process.env.ROLE_ADMIN,
color: {
Expand Down
2 changes: 1 addition & 1 deletion events/interactionCreate.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ module.exports = {
.setColor(config.color.e)
.setTimestamp();

await interaction.reply(messageEmbed);
await interaction.reply({ embeds: [messageEmbed] });
const logChannel = await GetLogChannel(interaction);
if (logChannel) {
logChannel.send({ embeds: [messageEmbed] });
Expand Down
63 changes: 54 additions & 9 deletions index.js → index.cjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const cron = require("node-cron");

const {
Client,
GatewayIntentBits,
Expand All @@ -16,8 +18,8 @@ const client = new Client({
partials: [Partials.Channel],
ws: {
properties: {
$os: "Untitled OS",
$browser: "Untitled Browser",
$os: "Uni OS",
$browser: "Uni Browser",
$device: "K8s on Proxmox VE 6.4 in Sibainu",
},
},
Expand All @@ -26,16 +28,18 @@ const client = new Client({
const fs = require("fs");

const config = require("./config.js");
const functions = { timeUtils: require("./lib/timeUtils.js"),logUtils: require("./lib/logUtils.js") };
const functions = {
timeUtils: require("./lib/timeUtils.js"),
logUtils: require("./lib/logUtils.js"),
};

client.conf = config;
client.func = functions;
client.fs = fs;

const cmdH = require(`./lib/commandUtils.js`);
client.commands = new Collection(); // Add this line to define the client.commands object
client.commands = new Collection();
cmdH.handling(client, fs, Collection, config);
// イベントハンドリング
const eventFiles = fs
.readdirSync("./events")
.filter((file) => file.endsWith(".js"));
Expand All @@ -46,7 +50,10 @@ for (const file of eventFiles) {
client.once(event.name, (...args) => event.execute(...args, client));
} catch (error) {
console.error(
`\u001b[31m[${functions.timeUtils.timeToJST(Date.now(), true)}]\u001b[0m\n`,
`\u001b[31m[${functions.timeUtils.timeToJST(
Date.now(),
true
)}]\u001b[0m\n`,
error
);
}
Expand All @@ -55,18 +62,53 @@ for (const file of eventFiles) {
client.on(event.name, (...args) => event.execute(...args, client));
} catch (error) {
console.error(
`\u001b[31m[${functions.timeUtils.timeToJST(Date.now(), true)}]\u001b[0m\n`,
`\u001b[31m[${functions.timeUtils.timeToJST(
Date.now(),
true
)}]\u001b[0m\n`,
error
);
}
}
}

const { rssGet } = require("./lib/rss.cjs");

cron.schedule("*/1-59 * * * *", async () => {
console.log("Cron job start");
const files = fs.readdirSync("./log/v1/feed");
for (const file of files) {
const datas = await JSON.parse(
fs.readFileSync(`./log/v1/feed/${file}.log`)
).data;
datas.forEach(async (data, index) => {
console.log(data.url);
const items = await rssGet(data.url);
const channel = client.channels.cache.get(file.replace(".log", ""));
for (const item of items) {
if (item.pubDate <= data.lastDate) continue;
console.log("send");
const embed = new EmbedBuilder()
.setTitle(item.title)
.setURL(item.link)
.setDescription(item.content)
.setColor(config.color.s)
.setTimestamp();
channel.send({ embeds: [embed] });
datas[index].lastDate = items[0].pubDate;
functions.logUtils.loging(datas, `v1/feed/${file}`);
}
});
}
});

client.login(config.token);

// エラー処理 (これ入れないとエラーで落ちる。本当は良くないかもしれない)
process.on("uncaughtException", (error) => {
console.error(`[${functions.timeUtils.timeToJST(Date.now(), true)}] ${error.stack}`);
console.error(
`[${functions.timeUtils.timeToJST(Date.now(), true)}] ${error.stack}`
);
const embed = new EmbedBuilder()
.setTitle("ERROR - uncaughtException")
.setDescription("```\n" + error.stack + "\n```")
Expand All @@ -79,7 +121,10 @@ process.on("uncaughtException", (error) => {

process.on("unhandledRejection", (reason, promise) => {
console.error(
`\u001b[31m[${functions.timeUtils.timeToJST(Date.now(), true)}] ${reason}\u001b[0m\n`,
`\u001b[31m[${functions.timeUtils.timeToJST(
Date.now(),
true
)}] ${reason}\u001b[0m\n`,
promise
);
const embed = new EmbedBuilder()
Expand Down
Loading

0 comments on commit 202c87e

Please sign in to comment.