working on rss processing
This commit is contained in:
parent
fb011e80c2
commit
0297fb12b6
@ -63,8 +63,10 @@
|
|||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"node-html-parser": "^7.0.1",
|
||||||
"nouislider": "^15.8.1",
|
"nouislider": "^15.8.1",
|
||||||
"preline": "^3.0.1",
|
"preline": "^3.0.1",
|
||||||
|
"rss-parser": "^3.13.0",
|
||||||
"tsconfig-paths": "^4.2.0",
|
"tsconfig-paths": "^4.2.0",
|
||||||
"vanilla-calendar-pro": "^3.0.4",
|
"vanilla-calendar-pro": "^3.0.4",
|
||||||
"winston": "^3.17.0"
|
"winston": "^3.17.0"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { Client, GatewayIntentBits, ActivityType } from "discord.js";
|
import { Client, GatewayIntentBits, ActivityType } from "discord.js";
|
||||||
|
import { triggerTask } from "@bot/task";
|
||||||
|
|
||||||
export const client = new Client({
|
export const client = new Client({
|
||||||
intents: [
|
intents: [
|
||||||
@ -13,6 +14,8 @@ client.on("ready", () => {
|
|||||||
throw Error("Client is null");
|
throw Error("Client is null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInterval(() => triggerTask(client), 5000);
|
||||||
|
|
||||||
client.user.setActivity("new sources", {type: ActivityType.Watching});
|
client.user.setActivity("new sources", {type: ActivityType.Watching});
|
||||||
console.log(`Discord Bot ${client.user.displayName} is online!`)
|
console.log(`Discord Bot ${client.user.displayName} is online!`)
|
||||||
});
|
});
|
||||||
|
40
src/bot/filter.ts
Normal file
40
src/bot/filter.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Filter, MatchingAlgorithms } from "../../generated/prisma";
|
||||||
|
|
||||||
|
export const any = (filter: Filter, input: string) => {
|
||||||
|
for (let word in input.split(" ")) {
|
||||||
|
word = filter.is_insensitive ? word.toLowerCase() : word;
|
||||||
|
const against = filter.is_insensitive ? filter.value.toLowerCase() : filter.value;
|
||||||
|
|
||||||
|
if (word.includes(against)) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const all = (filter: Filter, input: string) => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const literal = (filter: Filter, input: string) => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const regex = (filter: Filter, input: string) => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fuzzy = (filter: Filter, input: string) => {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mapAlgorithmToFunction = (filter: Filter, input: string) => {
|
||||||
|
switch (filter.matching_algorithm) {
|
||||||
|
case MatchingAlgorithms.ALL: return all(filter, input);
|
||||||
|
case MatchingAlgorithms.ANY: return any(filter, input);
|
||||||
|
case MatchingAlgorithms.EXACT: return literal(filter, input);
|
||||||
|
case MatchingAlgorithms.REGEX: return regex(filter, input);
|
||||||
|
case MatchingAlgorithms.FUZZY: return fuzzy(filter, input);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown algorithm: ${filter.matching_algorithm}`);
|
||||||
|
}
|
||||||
|
};
|
96
src/bot/task.ts
Normal file
96
src/bot/task.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { Client, EmbedBuilder, Guild, HexColorString, Channel as DiscordChannel, TextChannel } from "discord.js";
|
||||||
|
import RssParser from "rss-parser";
|
||||||
|
import { parse as HtmlParser } from "node-html-parser";
|
||||||
|
import { Feed, Filter, MessageStyle, Channel, MatchingAlgorithms } from "../../generated/prisma";
|
||||||
|
import * as filters from "@bot/filter";
|
||||||
|
import prisma from "@server/prisma";
|
||||||
|
|
||||||
|
export const triggerTask = async (client: Client) => {
|
||||||
|
for (const [_, guild] of client.guilds.cache) {
|
||||||
|
await processGuild(guild, client);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ExpandedFeed extends Feed {
|
||||||
|
channels: Channel[],
|
||||||
|
filters: Filter[],
|
||||||
|
message_style: MessageStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
const processGuild = async (guild: Guild, client: Client) => {
|
||||||
|
const feeds = await prisma.feed.findMany({
|
||||||
|
where: { guild_id: guild.id, active: true },
|
||||||
|
include: { channels: true, filters: true, message_style: true }
|
||||||
|
}) as ExpandedFeed[];
|
||||||
|
|
||||||
|
for (const feed of feeds) {
|
||||||
|
await processFeed(feed, client);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getParsedUrl = async (url: string) => {
|
||||||
|
const parser = new RssParser();
|
||||||
|
|
||||||
|
try { return parser.parseURL(url) }
|
||||||
|
catch (error) { return undefined }
|
||||||
|
};
|
||||||
|
|
||||||
|
const processFeed = async (feed: ExpandedFeed, client: Client) => {
|
||||||
|
const parsed = await getParsedUrl(feed.url);
|
||||||
|
if (!parsed) return;
|
||||||
|
|
||||||
|
for (const channelId of feed.channels.map(channel => channel.channel_id)) {
|
||||||
|
const channel = client.channels.cache.get(channelId);
|
||||||
|
if (channel) await processItems(parsed.items, feed, channel, client);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processItems = async (items: RssParser.Item[], feed: ExpandedFeed, channel: DiscordChannel, client: Client) => {
|
||||||
|
for (const item of items) {
|
||||||
|
await processItem(item, feed, channel, client);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processItem = async (item: RssParser.Item, feed: ExpandedFeed, channel: DiscordChannel, client: Client) => {
|
||||||
|
const filterPromises = feed.filters.map(async filter => {
|
||||||
|
return passesFilter(filter, item);
|
||||||
|
});
|
||||||
|
|
||||||
|
const filterResults = await Promise.all(filterPromises);
|
||||||
|
if (filterResults.includes(false)) return;
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder();
|
||||||
|
embed.setTitle(item.title ?? null);
|
||||||
|
embed.setDescription(item.contentSnippet ?? null);
|
||||||
|
embed.setImage(await getItemImageUrl(item.link ?? "") ?? null);
|
||||||
|
embed.setColor(feed.message_style.colour as HexColorString);
|
||||||
|
|
||||||
|
await (channel as TextChannel).send({ embeds: [embed] });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getItemImageUrl = async (url: string) => {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const html = HtmlParser.parse(await response.text());
|
||||||
|
|
||||||
|
const imageElement = html.querySelector("meta[property='og:image']");
|
||||||
|
if (!imageElement) return "";
|
||||||
|
|
||||||
|
return imageElement.getAttribute("content");
|
||||||
|
};
|
||||||
|
|
||||||
|
const passesFilter = async (filter: Filter, item: RssParser.Item) => {
|
||||||
|
if (!filter.matching_algorithm.trim()) return !filter.is_whitelist;
|
||||||
|
|
||||||
|
let matchFound = false;
|
||||||
|
|
||||||
|
if (filter.matching_algorithm === MatchingAlgorithms.ALL) {
|
||||||
|
matchFound = filters.all(filter, `${item.title} ${item.content}`);
|
||||||
|
} else {
|
||||||
|
matchFound = (
|
||||||
|
filters.mapAlgorithmToFunction(filter, item.title ?? "")
|
||||||
|
|| filters.mapAlgorithmToFunction(filter, item.content ?? "")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter.is_whitelist ? matchFound : !matchFound;
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user