mirror of
https://github.com/WCBROW01/zblock.git
synced 2025-12-12 04:28:07 -05:00
Add basic functionality (barely)
This commit is contained in:
11
Makefile
Normal file
11
Makefile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
CFLAGS = -Wall -Wextra -O2
|
||||||
|
LDFLAGS = -lpthread -lcurl -lmrss
|
||||||
|
|
||||||
|
SRC = $(wildcard *.c)
|
||||||
|
OBJ = $(SRC:.c=.o)
|
||||||
|
|
||||||
|
zblock: $(OBJ) /usr/local/lib/libdiscord.a
|
||||||
|
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean: rm -f $(OBJ) linuxbot
|
||||||
15
README.md
15
README.md
@ -1,2 +1,17 @@
|
|||||||
# zblock
|
# zblock
|
||||||
A simple, lightweight Discord RSS bot
|
A simple, lightweight Discord RSS bot
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- curl
|
||||||
|
- concord
|
||||||
|
- libmrss
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
[ ] Add build instructions
|
||||||
|
[ ] Get all new feeds, not just the first one
|
||||||
|
[ ] Set permissions for add and remove command
|
||||||
|
[ ] Import feeds from disk on startup
|
||||||
|
[ ] Remove all feeds if bot is removed from a guild
|
||||||
|
[ ] Implement list command
|
||||||
|
[ ] Implement remove command
|
||||||
|
|
||||||
|
|||||||
24
config.json
Normal file
24
config.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"logging": {
|
||||||
|
"level": "info",
|
||||||
|
"filename": "bot.log",
|
||||||
|
"quiet": true,
|
||||||
|
"overwrite": true,
|
||||||
|
"use_color": true,
|
||||||
|
"http": {
|
||||||
|
"enable": false,
|
||||||
|
"filename": "http.log"
|
||||||
|
},
|
||||||
|
"disable_modules": ["WEBSOCKETS", "USER_AGENT"]
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"token": "YOUR-BOT-TOKEN",
|
||||||
|
"default_prefix": {
|
||||||
|
"enable": false,
|
||||||
|
"prefix": "YOUR-COMMANDS-PREFIX"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"zblock": {
|
||||||
|
"database_path": "feeds"
|
||||||
|
}
|
||||||
|
}
|
||||||
244
main.c
Normal file
244
main.c
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <concord/discord.h>
|
||||||
|
#include <concord/log.h>
|
||||||
|
|
||||||
|
#include <mrss.h>
|
||||||
|
|
||||||
|
// Function pointer type for commands
|
||||||
|
typedef void (*command_func)(struct discord *, const struct discord_interaction *);
|
||||||
|
|
||||||
|
struct bot_command {
|
||||||
|
struct discord_create_global_application_command cmd;
|
||||||
|
const command_func func;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define P99_PROTECT(...) __VA_ARGS__
|
||||||
|
|
||||||
|
// absolutely ridiculous preprocessor hack.
|
||||||
|
#define _CREATE_OPTIONS(options) &(struct discord_application_command_options) { .size = sizeof((struct discord_application_command_option[]) options) / sizeof(struct discord_application_command_option), .array = (struct discord_application_command_option[]) options }
|
||||||
|
#define CREATE_OPTIONS(...) _CREATE_OPTIONS(P99_PROTECT(__VA_ARGS__))
|
||||||
|
|
||||||
|
#define BOT_COMMAND_NOT_IMPLEMENTED() do { \
|
||||||
|
struct discord_interaction_response res = { \
|
||||||
|
.type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE, \
|
||||||
|
.data = &(struct discord_interaction_callback_data) { \
|
||||||
|
.content = "This command has not been implemented yet." \
|
||||||
|
} \
|
||||||
|
}; \
|
||||||
|
discord_create_interaction_response(client, event->id, event->token, &res, NULL); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
struct feed_info {
|
||||||
|
char *title;
|
||||||
|
char *url;
|
||||||
|
char *pubDate;
|
||||||
|
u64snowflake guild_id;
|
||||||
|
u64snowflake channel_id;
|
||||||
|
unsigned timer_id;
|
||||||
|
unsigned feed_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
void feed_info_free(struct feed_info *feed) {
|
||||||
|
free(feed->title);
|
||||||
|
free(feed->url);
|
||||||
|
free(feed->pubDate);
|
||||||
|
free(feed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *database_path = "feeds";
|
||||||
|
|
||||||
|
// default interval for the feed retrieval timer
|
||||||
|
#define TIMER_INTERVAL 600
|
||||||
|
|
||||||
|
// this just barely works at the moment
|
||||||
|
static void timer_retrieve_feeds(struct discord *client, struct discord_timer *timer) {
|
||||||
|
struct feed_info *feed = timer->data;
|
||||||
|
|
||||||
|
struct mrss_t *mrss_feed;
|
||||||
|
if (mrss_parse_url(feed->url, &mrss_feed)) return; // do nothing on error
|
||||||
|
|
||||||
|
// get publication date and check if it is the same (change this later this is lazy and won't get all new feeds)
|
||||||
|
if (strcmp(mrss_feed->item->pubDate, feed->pubDate)) {
|
||||||
|
// Send new entry in the feed
|
||||||
|
char msg[DISCORD_MAX_MESSAGE_LEN];
|
||||||
|
snprintf(msg, sizeof(msg), "## %s\n### %s\n%s", mrss_feed->title, mrss_feed->item->title, mrss_feed->item->link);
|
||||||
|
struct discord_create_message res = { .content = msg };
|
||||||
|
discord_create_message(client, feed->channel_id, &res, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
mrss_free(mrss_feed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bot_command_add(struct discord *client, const struct discord_interaction *event) {
|
||||||
|
char msg[DISCORD_MAX_MESSAGE_LEN];
|
||||||
|
struct feed_info *feed = calloc(1, sizeof(struct feed_info));
|
||||||
|
if (!feed) {
|
||||||
|
snprintf(msg, sizeof(msg), "Error adding feed: %s", strerror(errno));
|
||||||
|
goto send_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
feed->url = strdup(event->data->options->array[0].value);
|
||||||
|
if (!feed->url) {
|
||||||
|
snprintf(msg, sizeof(msg), "Error adding feed: %s", strerror(errno));
|
||||||
|
feed_info_free(feed);
|
||||||
|
goto send_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
mrss_t *mrss_feed = NULL;
|
||||||
|
if(mrss_parse_url(feed->url, &mrss_feed)) {
|
||||||
|
// error here figure this out
|
||||||
|
feed_info_free(feed);
|
||||||
|
goto send_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
feed->title = mrss_feed->title;
|
||||||
|
feed->pubDate = mrss_feed->item->pubDate;
|
||||||
|
feed->feed_id = rand();
|
||||||
|
|
||||||
|
|
||||||
|
char file_path[PATH_MAX];
|
||||||
|
// check if we ran out of path
|
||||||
|
int path_len = snprintf(file_path, sizeof(file_path), "%s/%lu/%lu/%x", database_path, event->guild_id, event->channel_id, feed->feed_id);
|
||||||
|
|
||||||
|
FILE *fp = fopen(file_path, "w");
|
||||||
|
if (!fp) {
|
||||||
|
snprintf(msg, sizeof(msg), "Error adding feed: %s", strerror(errno));
|
||||||
|
mrss_free(mrss_feed);
|
||||||
|
feed_info_free(feed);
|
||||||
|
goto send_msg;
|
||||||
|
}
|
||||||
|
fprintf(fp, "title=%s\nurl=%s\npubDate=%s\n", feed->title, feed->url, feed->pubDate);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
// spawn the timer for this feed
|
||||||
|
feed->timer_id = discord_timer_interval(client, timer_retrieve_feeds, NULL, feed, 0, TIMER_INTERVAL, -1);
|
||||||
|
|
||||||
|
mrss_free(mrss_feed);
|
||||||
|
|
||||||
|
// send the confirmation message
|
||||||
|
snprintf(msg, sizeof(msg), "The following feed has been successfully added to this channel:\n`%s`", feed->url);
|
||||||
|
|
||||||
|
send_msg:
|
||||||
|
struct discord_interaction_response res = {
|
||||||
|
.type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
|
.data = &(struct discord_interaction_callback_data) {
|
||||||
|
.content = msg
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
discord_create_interaction_response(client, event->id, event->token, &res, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bot_command_list(struct discord *client, const struct discord_interaction *event) {
|
||||||
|
BOT_COMMAND_NOT_IMPLEMENTED();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bot_command_help(struct discord *client, const struct discord_interaction *event) {
|
||||||
|
char msg[DISCORD_MAX_MESSAGE_LEN];
|
||||||
|
|
||||||
|
// intro message
|
||||||
|
snprintf(
|
||||||
|
msg, sizeof(msg),
|
||||||
|
"Hello %s, welcome to zblock, a lightweight RSS bot for Discord!\n"
|
||||||
|
"You can find the source code for this bot at https://github.com/WCBROW01/zblock\n"
|
||||||
|
"Please submit any bugs or issues there, or feel free to make a pull request!",
|
||||||
|
event->user ? event->user->username : event->member->user->username
|
||||||
|
);
|
||||||
|
|
||||||
|
struct discord_interaction_response res = {
|
||||||
|
.type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
|
.data = &(struct discord_interaction_callback_data) {
|
||||||
|
.content = msg
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
discord_create_interaction_response(client, event->id, event->token, &res, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct bot_command commands[] = {
|
||||||
|
{
|
||||||
|
.cmd = {
|
||||||
|
.name = "add",
|
||||||
|
.description = "Add an RSS feed",
|
||||||
|
.default_permission = true,
|
||||||
|
.options = CREATE_OPTIONS({
|
||||||
|
{
|
||||||
|
.type = DISCORD_APPLICATION_OPTION_STRING,
|
||||||
|
.name = "url",
|
||||||
|
.description = "The URL of your feed",
|
||||||
|
.required = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
.func = &bot_command_add
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.cmd = {
|
||||||
|
.name = "list",
|
||||||
|
.description = "List the RSS feeds in the current channel",
|
||||||
|
.default_permission = true
|
||||||
|
},
|
||||||
|
.func = &bot_command_list
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.cmd = {
|
||||||
|
.name = "help",
|
||||||
|
.description = "Get help on how to use the bot",
|
||||||
|
.dm_permission = true
|
||||||
|
},
|
||||||
|
.func = &bot_command_help
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void on_ready(struct discord *client, const struct discord_ready *event) {
|
||||||
|
log_info("Logged in as %s!", event->user->username);
|
||||||
|
|
||||||
|
// create commands
|
||||||
|
for (struct bot_command *i = commands; i < commands + sizeof(commands) / sizeof(*commands); ++i) {
|
||||||
|
discord_create_global_application_command(client, event->application->id, &i->cmd, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create feed retrieval timers
|
||||||
|
|
||||||
|
|
||||||
|
log_info("Ready!");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_interaction(struct discord *client, const struct discord_interaction *event) {
|
||||||
|
if (event->type != DISCORD_INTERACTION_APPLICATION_COMMAND)
|
||||||
|
return; // not a slash command
|
||||||
|
|
||||||
|
// invoke the command
|
||||||
|
for (struct bot_command *i = commands; i < commands + sizeof(commands) / sizeof(*commands); ++i) {
|
||||||
|
if (!strcmp(event->data->name, i->cmd.name)) {
|
||||||
|
i->func(client, event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not a real command
|
||||||
|
struct discord_interaction_response res = {
|
||||||
|
.type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
|
.data = &(struct discord_interaction_callback_data) {
|
||||||
|
.content = "Invalid command, contact the maintainer of this bot."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
discord_create_interaction_response(client, event->id, event->token, &res, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
srand(time(NULL));
|
||||||
|
struct discord *client = discord_config_init("config.json");
|
||||||
|
discord_set_on_ready(client, &on_ready);
|
||||||
|
discord_set_on_interaction_create(client, &on_interaction);
|
||||||
|
discord_run(client);
|
||||||
|
discord_cleanup(client);
|
||||||
|
ccord_global_cleanup();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user