diff options
author | Ratakor <ratakor@disroot.org> | 2023-06-12 00:29:14 +0200 |
---|---|---|
committer | Ratakor <ratakor@disroot.org> | 2023-06-12 00:29:14 +0200 |
commit | 264964ec139987d544a31b39f5e112d0ea3072c3 (patch) | |
tree | a387f42394c412a85f15308bda8cc624100d62d1 | |
parent | 8320fd90666db07b5a90f5c3749279c6bb205a5e (diff) |
Lot of changes, read commit messagev0.0.8
Replace warn() and die() with WARN and DIE macros.
Replace emsg() in raids.c with discord_send_message().
Add efopen() in util.c.
Add all Scream of Terra member to slayers[] in raids.c.
Add automatic ping for overcap_msg() instead of just message with name.
Add stats_admin command.
Fix stats command -> need to use cJSON lib.
Fix info to not output kingdom when it's null.
Fix typo in parse_line() in stats.c.
Remove ecalloc().
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | src/cmd_help.c | 13 | ||||
-rw-r--r-- | src/cmd_info.c | 10 | ||||
-rw-r--r-- | src/cmd_lbraid.c | 11 | ||||
-rw-r--r-- | src/cmd_leaderboard.c | 8 | ||||
-rw-r--r-- | src/cmd_source.c | 8 | ||||
-rw-r--r-- | src/cmd_uraid.c | 9 | ||||
-rw-r--r-- | src/init.c | 51 | ||||
-rw-r--r-- | src/main.c | 4 | ||||
-rw-r--r-- | src/nolan.h | 5 | ||||
-rw-r--r-- | src/ocr.c | 33 | ||||
-rw-r--r-- | src/raids.c | 156 | ||||
-rw-r--r-- | src/stats.c | 180 | ||||
-rw-r--r-- | src/util.c | 41 | ||||
-rw-r--r-- | src/util.h | 30 |
16 files changed, 315 insertions, 251 deletions
@@ -11,9 +11,10 @@ OBJS := ${SRCS:%=${BUILD_DIR}/%.o} DISCORDLIBS = -ldiscord -lcurl -lpthread TESSLIBS = -ltesseract -lleptonica GDLIBS = -lgd -lpng -lz -ljpeg -lfreetype -lm +CJSONLIB = -lcjson INCS = -I/usr/local/include -I/usr/include -LIBS = -L/usr/lib -L/usr/local/lib ${DISCORDLIBS} ${TESSLIBS} ${GDLIBS} +LIBS = -L/usr/lib -L/usr/local/lib ${DISCORDLIBS} ${TESSLIBS} ${GDLIBS} ${CJSONLIB} DEBUG_FLAGS = -O0 -g -W -Wall -Wmissing-prototypes CFLAGS += -std=c99 -pedantic -D_DEFAULT_SOURCE ${INCS} @@ -5,14 +5,16 @@ A discord bot for Orna. - [concord](https://github.com/Cogmasters/concord) - [tesseract](https://github.com/tesseract-ocr/tesseract) - [gd](https://github.com/libgd/libgd) +- [cJSON](https://github.com/DaveGamble/cJSON) ## TODO ### features -- add option to correct stats ? +- add cmd_correct to correct stats - ascensions (track mats) ### chore +- change players' stats to unsigned long - add documentation - rework DIFF in ocr.c - rework roles diff --git a/src/cmd_help.c b/src/cmd_help.c index f452b88..766cf08 100644 --- a/src/cmd_help.c +++ b/src/cmd_help.c @@ -24,20 +24,16 @@ help(char *buf, size_t siz) strlcpy(buf, "Post a screenshot of your stats to ", siz); for (i = 0; i < len; i++) { p = strchr(buf, '\0');; - rsiz = snprintf(p, siz, "<#%lu> ", stats_ids[i]); - if (rsiz >= siz) { - warn("nolan: truncation happened while writing help, \ -probably way too much channels for stats\n"); - } + snprintf(p, siz, "<#%lu> ", stats_ids[i]); if (i < len - 1) strlcat(buf, "or ", siz); } - strlcat(buf, "to enter the database.\n", siz); + strlcat(buf, "so I can have a look at your stats π\n", siz); strlcat(buf, "Commands:\n", siz); strlcat(buf, "\t/stats *screenshot*\n", siz); strlcat(buf, "\t/info *[[@]user]*\n", siz); strlcat(buf, "\t/leaderboard *category*\n", siz); - /* catstr(buf, "\t/correct [category] [corrected value]\n", siz); */ + /* strlcat(buf, "\t/correct *category* *corrected value*\n", siz); */ strlcat(buf, "\t/source *[kingdom]*\n", siz); strlcat(buf, "\t/uraid *username* (only for Scream of Terra)\n", siz); strlcat(buf, "\t/lbraid (only for Scream of Terra)\n", siz); @@ -47,7 +43,8 @@ probably way too much channels for stats\n"); strlcat(buf, PREFIX, siz); rsiz = strlcat(buf, " instead of /.", siz); if (rsiz >= siz) - warn("nolan: truncation happened while writing help\n"); + WARN("string truncation\n\ +\033[33mhint:\033[39m this is probably because stats_ids is too big"); } void diff --git a/src/cmd_info.c b/src/cmd_info.c index 787a9d1..b038f28 100644 --- a/src/cmd_info.c +++ b/src/cmd_info.c @@ -98,9 +98,13 @@ write_info(char *buf, size_t siz, const Player *player) /* -2 to not include upadte and userid */ for (i = 0; i < LENGTH(fields) - 2; i++) { - if (i <= 1) { /* name and kingdom */ - snprintf(p, siz, "%s: %s\n", fields[i], - ((char **)player)[i]); + if (i == 0) { /* name */ + snprintf(p, siz, "%s: %s\n", fields[i], player->name); + } else if (i == 1) { /* kingdom */ + if (strcmp(player->kingdom, "(null)") != 0) { + snprintf(p, siz, "%s: %s\n", fields[i], + player->kingdom); + } } else if (i == 7) { /* playtime */ plt = playtime_to_str(((long *)player)[i]); snprintf(p, siz, "%s: %s\n", fields[i], plt); diff --git a/src/cmd_lbraid.c b/src/cmd_lbraid.c index ba2cd38..d68a9e3 100644 --- a/src/cmd_lbraid.c +++ b/src/cmd_lbraid.c @@ -30,9 +30,7 @@ parse_file(char *fname, Slayer slayers[], size_t *nslayers) unsigned int i; unsigned long dmg; - if ((fp = fopen(fname, "r")) == NULL) - return; - + fp = efopen(fname, "r"); while (fgets(line, LINE_SIZE, fp)) { endname = strchr(line, DELIM); dmg = strtoul(endname + 1, NULL, 10); @@ -64,9 +62,8 @@ load_files(Slayer slayers[], size_t *nslayers) for (i = 0; i < 6; i++) { snprintf(fname, sizeof(fname), "%s%ld.csv", RAIDS_FOLDER, day - i); - if (file_exists(fname)) { + if (file_exists(fname)) parse_file(fname, slayers, nslayers); - } } } @@ -94,10 +91,10 @@ write_lbraid(char *buf, int siz, Slayer slayers[], size_t nslayers) for (i = 0; i < lb_max; i++) { siz -= snprintf(p, siz, "%d. %s: %'lu damage\n", i, slayers[i].name, slayers[i].damage); - if (siz <= 0) - warn("nolan: string truncation in %s\n", __func__); p = strchr(buf, '\0'); } + if (siz <= 0) + WARN("string truncation"); } void diff --git a/src/cmd_leaderboard.c b/src/cmd_leaderboard.c index 11a09bc..e40fdaa 100644 --- a/src/cmd_leaderboard.c +++ b/src/cmd_leaderboard.c @@ -195,8 +195,8 @@ write_leaderboard(char *buf, size_t siz, u64snowflake userid) } rsiz = strlcat(buf, player, siz); if (rsiz >= siz) { - warn("nolan: string truncation happened while writing \ -leaderboard.\nThis is probably because LB_MAX is too big.\n"); + WARN("string truncation\n\ +\033[33mhint:\033[39m this is probably because LB_MAX is too big"); } } @@ -209,8 +209,8 @@ leaderboard.\nThis is probably because LB_MAX is too big.\n"); write_player(player, psiz, i, 1); rsiz = strlcat(buf, player, siz); if (rsiz >= siz) { - warn("nolan: string truncation happened while writing \ -leaderboard.\nThis is probably because LB_MAX is too big.\n"); + WARN("string truncation\n\ +\033[33mhint:\033[39m this is probably because LB_MAX is too big"); } } } diff --git a/src/cmd_source.c b/src/cmd_source.c index d17d288..6e9d2c9 100644 --- a/src/cmd_source.c +++ b/src/cmd_source.c @@ -35,9 +35,7 @@ load_source(size_t *fszp) char *res, line[LINE_SIZE], *end; size_t mfsz = LINE_SIZE + nplayers * LINE_SIZE; - if ((fp = fopen(STATS_FILE, "r")) == NULL) - die("nolan: Failed to open %s (read)\n", STATS_FILE); - + fp = efopen(STATS_FILE, "r"); res = emalloc(mfsz); *res = '\0'; while (fgets(line, LINE_SIZE, fp) != NULL) { @@ -61,9 +59,7 @@ load_sorted_source(size_t *fszp, char *kingdom) char *res, line[LINE_SIZE], *kd, *endkd, *end; size_t mfsz = LINE_SIZE + nplayers * LINE_SIZE; - if ((fp = fopen(STATS_FILE, "r")) == NULL) - die("nolan: Failed to open %s (read)\n", STATS_FILE); - + fp = efopen(STATS_FILE, "r"); res = emalloc(mfsz); fgets(line, LINE_SIZE, fp); /* fields name */ /* skip everything after codex */ diff --git a/src/cmd_uraid.c b/src/cmd_uraid.c index 23c3b52..788af07 100644 --- a/src/cmd_uraid.c +++ b/src/cmd_uraid.c @@ -49,9 +49,7 @@ parse_file(char *fname, char *username) unsigned long dmg; char line[LINE_SIZE], *endname; - if ((fp = fopen(fname, "r")) == NULL) - return 0; - + fp = efopen(fname, "r"); while (fgets(line, LINE_SIZE, fp)) { endname = strchr(line, DELIM); dmg = strtoul(endname + 1, NULL, 10); @@ -77,9 +75,8 @@ load_files(char *username, unsigned long *dmgs) for (i = 0; i < 6; i++) { snprintf(fname, sizeof(fname), "%s%ld.csv", RAIDS_FOLDER, day - i); - if (file_exists(fname)) { + if (file_exists(fname)) dmgs[i] = parse_file(fname, username); - } } return dmgs; @@ -114,7 +111,7 @@ write_uraid(char *buf, int siz, char *username, unsigned long *dmgs) } siz -= snprintf(p, siz, "\nTotal: %'lu damage\n", total); if (siz <= 0) - warn("nolan: string truncation in %s\n", __func__); + WARN("string truncation"); } void @@ -10,17 +10,18 @@ static Player load_player(unsigned int line); void create_folders(void) { - if (!file_exists(SAVE_FOLDER)) { - if (mkdir(SAVE_FOLDER, 0755) == -1) - die("nolan: Failed to create %s\n", SAVE_FOLDER); - } - if (!file_exists(IMAGES_FOLDER)) { - if (mkdir(IMAGES_FOLDER, 0755) == -1) - die("nolan: Failed to create %s\n", IMAGES_FOLDER); - } - if (!file_exists(RAIDS_FOLDER)) { - if (mkdir(RAIDS_FOLDER, 0755) == -1) - die("nolan: Failed to create %s\n", RAIDS_FOLDER); + unsigned int i; + const char *folders[] = { + SAVE_FOLDER, + IMAGES_FOLDER, + RAIDS_FOLDER, + }; + + for (i = 0; i < LENGTH(folders); i++) { + if (file_exists(folders[i])) + continue; + if (mkdir(folders[i], 0755) == -1) + DIE("failed to create %s\n", folders[i]); } } @@ -38,7 +39,9 @@ create_stats_file(void) size = ftell(fp); } - if (size == 0 && (fp = fopen(STATS_FILE, "w")) != NULL) { + if (size == 0) { + if (fp) fclose(fp); + fp = efopen(STATS_FILE, "w"); for (i = 0; i < LENGTH(fields) - 1; i++) fprintf(fp, "%s%c", fields[i], DELIM); fprintf(fp, "%s\n", fields[LENGTH(fields) - 1]); @@ -51,8 +54,8 @@ create_slash_commands(struct discord *client) { #ifndef DEVEL create_slash_help(client); - /* create_slash_stats(client); */ - /* create_slash_stats_admin(client); */ + create_slash_stats(client); + create_slash_stats_admin(client); create_slash_info(client); create_slash_leaderboard(client); create_slash_source(client); @@ -70,14 +73,13 @@ load_player(unsigned int line) unsigned int i = 0; if (line <= 1) - die("nolan: Tried to load the description line as a player\n"); - if ((fp = fopen(STATS_FILE, "r")) == NULL) - die("nolan: Failed to open %s (read)\n", STATS_FILE); + DIE("tried to load the description line as a player"); + fp = efopen(STATS_FILE, "r"); while (i++ < line && (p = fgets(buf, LINE_SIZE, fp)) != NULL); fclose(fp); if (p == NULL) - die("nolan: Line %d is not present in %s\n", line, STATS_FILE); + DIE("line %d is not present in %s", line, STATS_FILE); i = 0; delim = p; @@ -97,8 +99,10 @@ load_player(unsigned int line) p = delim + 1; i++; } - if (i != LENGTH(fields) - 1) - die("nolan: Player on line %d is missing a field\n", line); + if (i != LENGTH(fields) - 1) { + DIE("player in %s on line %d is missing a field", STATS_FILE, + line); + } player.userid = strtoul(p, NULL, 10); return player; @@ -111,16 +115,13 @@ init_players(void) char buf[LINE_SIZE]; unsigned int i; - if ((fp = fopen(STATS_FILE, "r")) == NULL) - die("nolan: Failed to open %s (read)\n", STATS_FILE); - + fp = efopen(STATS_FILE, "r"); while (fgets(buf, LINE_SIZE, fp)) nplayers++; nplayers--; /* first line is not a player */ if (nplayers > MAX_PLAYERS) - die("nolan: There is too much players to load (max:%d)\n", - MAX_PLAYERS); + DIE("there is too much players to load (max:%lu)", MAX_PLAYERS); for (i = 0; i < nplayers; i++) players[i] = load_player(i + 2); @@ -34,7 +34,7 @@ const char *fields[] = { }; int -main(void) +main(int argc, char *argv[]) { char *src[] = { "src", "source" }; char *lb[] = { "lb", "leaderboard" }; @@ -42,7 +42,7 @@ main(void) #ifndef DEVEL if (getuid() != 0) - die("Please run nolan as root\n"); + DIE("please run %s as root", argv[0]); #endif /* DEVEL */ setlocale(LC_NUMERIC, ""); diff --git a/src/nolan.h b/src/nolan.h index 66d8e22..9bff5b2 100644 --- a/src/nolan.h +++ b/src/nolan.h @@ -12,14 +12,17 @@ #define MAX_MESSAGE_LEN 2000 + 1 #define MAX_USERNAME_LEN 32 + 1 #define MAX_KINGDOM_LEN 32 + 1 + #ifdef DEVEL #define SAVE_FOLDER "./" #else #define SAVE_FOLDER "/var/lib/nolan/" #endif /* DEVEL */ + #define IMAGES_FOLDER SAVE_FOLDER "images/" #define RAIDS_FOLDER SAVE_FOLDER "raids/" #define STATS_FILE SAVE_FOLDER FILENAME + #define ROLE_GUILD_ID 999691133103919135 /* this is only for to Orna FR */ /* ALL FIELDS MUST HAVE THE SAME SIZE */ @@ -72,7 +75,7 @@ void on_ready(struct discord *client, const struct discord_ready *event); void on_message(struct discord *client, const struct discord_message *event); /* ocr.c */ -void curl(char *url, char *fname); +unsigned int curl(char *url, char *fname); int crop(char *fname, int type); char *ocr(char *fname, char *lang); @@ -20,10 +20,11 @@ write_data(void *ptr, size_t size, size_t nmemb, void *stream) return written; } -void +unsigned int curl(char *url, char *fname) { CURL *handle; + CURLcode ret; FILE *fp; curl_global_init(CURL_GLOBAL_ALL); @@ -33,15 +34,15 @@ curl(char *url, char *fname) curl_easy_setopt(handle, CURLOPT_URL, url); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_data); - if ((fp = fopen(fname, "wb")) == NULL) - die("nolan: Failed to open %s\n", fname); - + fp = efopen(fname, "wb"); curl_easy_setopt(handle, CURLOPT_WRITEDATA, fp); - curl_easy_perform(handle); + ret = curl_easy_perform(handle); fclose(fp); curl_easy_cleanup(handle); curl_global_cleanup(); + + return ret; } int @@ -72,31 +73,25 @@ crop(char *fname, int type) gdImage *im, *cropped; gdRect rect; - if ((fp = fopen(fname, "rb")) == NULL) - die("nolan: Failed to open %s\n", fname); - + fp = efopen(fname, "rb"); if (type == 0) im = gdImageCreateFromJpeg(fp); else im = gdImageCreateFromPng(fp); fclose(fp); + if (im == NULL) return 1; - if (write_rect(&rect, im) == 1) return 1; cropped = gdImageCrop(im, &rect); - - if ((fp = fopen(fname, "wb")) == NULL) - die("nolan: Failed to open %s\n", fname); - + fp = efopen(fname, "wb"); if (type == 0) gdImageJpeg(cropped, fp, 100); else gdImagePng(cropped, fp); fclose(fp); - gdImageDestroy(cropped); gdImageDestroy(im); @@ -110,16 +105,18 @@ ocr(char *fname, char *lang) PIX *img; char *txt_ocr, *txt_out; - if ((img = pixRead(fname)) == NULL) - die("nolan: Error reading image\n"); + if ((img = pixRead(fname)) == NULL) { + WARN("failed to read image (%s)", fname); + return NULL; + } handle = TessBaseAPICreate(); if (TessBaseAPIInit3(handle, NULL, lang) != 0) - die("nolan: Error initialising tesseract\n"); + DIE("failed to init tesseract (lang:%s)", lang); TessBaseAPISetImage2(handle, img); if (TessBaseAPIRecognize(handle, NULL) != 0) - die("nolan: Error in tesseract recognition\n"); + DIE("failed tesseract recognition"); txt_ocr = TessBaseAPIGetUTF8Text(handle); txt_out = strdup(txt_ocr); diff --git a/src/raids.c b/src/raids.c index 64f45f2..80fa593 100644 --- a/src/raids.c +++ b/src/raids.c @@ -1,3 +1,4 @@ +#include <stdarg.h> #include <stdlib.h> #include <string.h> #include <time.h> @@ -6,7 +7,14 @@ #define DAMAGE_CAP 300000000 / 7 /* daily */ -static void emsg(struct discord *client, const struct discord_message *event); +struct Slayers { + char *name; + u64snowflake userid; +}; + +static void discord_send_message(struct discord *client, + const u64snowflake channel_id, + const char *fmt, ...); static char *skip_to_slayers(char *txt); static char *trim_name(char *name); static unsigned long trim_dmg(char *str); @@ -16,10 +24,10 @@ static unsigned long adjust(unsigned long dmg, char *raid); static void save_to_new_file(Slayer slayers[], size_t nslayers, char *fname, char *raid); static void overcap_msg(char *name, unsigned long dmg, struct discord *client, - const struct discord_message *event); + const u64snowflake channel_id); static void save_to_file(Slayer slayers[], size_t nslayers, char *raid, struct discord *client, - const struct discord_message *event); + const u64snowflake channel_id); static const char *delims[] = { "+ Raid options", @@ -38,19 +46,70 @@ static const char *garbageslayer[] = { "θ¨ δΌ θ
" }; +const struct Slayers slayers[] = { + { "Davethegray", 618842282564648976 }, + { "SmittyWerbenJaeger", 372349296772907008 }, + { "Damaquandey", 237305375211257866 }, + { "Fhullegans", 401213200688742412 }, + { "PhilipXIVTheGintonicKnight", 653570978068299777 }, + { "Heatnick", 352065341901504512 }, + { "Tazziekat", 669809258153639937 }, + { "Basic", 274260433521737729 }, + { "KovikFrunobulax", 589110637267779584 }, + { "GoDLiKeKiLL", 425706840060592130 }, + { "Shazaaamm", 516711375330869367 }, + { "Yiri", 179396886254452736 }, + { "MilesandroIlgnorante", 163351655478329344 }, + { "HurricaneHam", 245010787817488384 }, + { "SneakPeek", 559664517198381057 }, + { "Ensseric", 659545497144655903 }, + { "Mijikai", 163048434478088192 }, + { "BigYoshi", 247796779804917770 }, + { "Ratakor", 277534384175841280 }, + { "oxDje", 452860420034789396 }, + { "LordDroopy", 704457235467730986 }, + { "discosoutmurdersin", 609770024944664657 }, + { "EchinChanfromHELL", 819244261760565280 }, + { "ANIMAL", 222464347568472064 }, + { "Soreloser", 345516161838088192 }, + { "KyzeMythos", 189463147571052544 }, + { "ElucidatorS", 636975696186441739 }, + { "BrewmasterAalst", 155893692740141056 }, + { "Tiroc", 519282218057596929 }, + { "Kyzee", 685267547377106990 }, + { "Shadowssin", 265837021400924162 }, + { "Burtonlol", 353969780702576641 }, + { "Wingeren", 75695406381543424 }, + { "Schmiss", 460552631778279457 }, + { "Bloodshade", 329400042324623371 }, + { "Randylittle", 555760640329777153 }, + { "Sunveela", 269171964105457665 }, + { "SaiSenpai", 958922552284172349 }, + { "Jakealope", 164209517926678528 }, + { "CleverCaitlin", 297650487585406976 }, + { "Demmo", 807849505105248287 }, /* Demm974 */ +}; + +/* this should be in util.c but it's only used here*/ void -emsg(struct discord *client, const struct discord_message *event) +discord_send_message(struct discord *client, const u64snowflake channel_id, + const char *fmt, ...) { char buf[MAX_MESSAGE_LEN]; + size_t rsiz; + va_list ap; + + va_start(ap, fmt); + rsiz = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + if (rsiz >= sizeof(buf)) + WARN("string truncation"); - warn("nolan: Failed to read raid image\n"); - snprintf(buf, sizeof(buf), - "Error: Failed to read image <@%lu>.\nFix me <@%lu>", - event->author->id, ADMIN); struct discord_create_message msg = { .content = buf }; - discord_create_message(client, event->channel_id, &msg, NULL); + discord_create_message(client, channel_id, &msg, NULL); } char * @@ -89,7 +148,7 @@ skip_to_slayers(char *txt) return NULL; } -/* trim everything that is not a letter or a space */ +/* trim everything that is not a letter ~~or a space~~ */ char * trim_name(char *name) { @@ -101,7 +160,7 @@ trim_name(char *name) r = name; w = name; do { - if (*r == ' ' || (*r >= 'A' && *r <= 'Z') || (*r >= 'a' && *r <= 'z')) + if ((*r >= 'A' && *r <= 'Z') || (*r >= 'a' && *r <= 'z')) *w++ = *r; } while (*r++); *w = '\0'; @@ -223,8 +282,8 @@ save_to_new_file(Slayer slayers[], size_t nslayers, char *fname, char *raid) { FILE *fp; unsigned int i; - if ((fp = fopen(fname, "w")) == NULL) - die("nolan: Failed to open %s\n", fname); + + fp = efopen(fname, "w"); for (i = 0; i < nslayers; i++) { fprintf(fp, "%s%c%lu\n", slayers[i].name, DELIM, adjust(slayers[i].damage, raid)); @@ -233,28 +292,35 @@ save_to_new_file(Slayer slayers[], size_t nslayers, char *fname, char *raid) fclose(fp); } +/* TODO: use a weekly cap instead */ void overcap_msg(char *name, unsigned long dmg, struct discord *client, - const struct discord_message *event) + const u64snowflake channel_id) { - char buf[MAX_MESSAGE_LEN]; + unsigned int i = 0; + size_t len; if (dmg < DAMAGE_CAP) return; - /* TODO: match name with userid + use a weekly cap instead */ - snprintf(buf, sizeof(buf), "%s has overcapped the limit by %'lu \ -damage he is now at %'lu damage.", name, dmg - DAMAGE_CAP, dmg); - - struct discord_create_message msg = { - .content = buf - }; - discord_create_message(client, event->channel_id, &msg, NULL); + len = strlen(name) - 3; + while (i < LENGTH(slayers) && strncasecmp(name, slayers[i].name, len) != 0) + i++; + + if (i == MAX_SLAYERS) { + discord_send_message(client, channel_id, + "%s has overcapped the limit by %'lu \ +damage, he is now at %'lu damage.", name, dmg - DAMAGE_CAP, dmg); + } else { + discord_send_message(client, channel_id, + "<@%lu> has overcapped the limit by %'lu \ +damage, he is now at %'lu damage.", slayers[i].userid, dmg - DAMAGE_CAP, dmg); + } } void save_to_file(Slayer slayers[], size_t nslayers, char *raid, - struct discord *client, const struct discord_message *event) + struct discord *client, const u64snowflake channel_id) { FILE *w, *r; char line[LINE_SIZE], *endname, fname[128], tmpfname[128]; @@ -262,7 +328,6 @@ save_to_file(Slayer slayers[], size_t nslayers, char *raid, unsigned long olddmg, newdmg; long day = time(NULL) / 86400; - /* assert with rsiz >= siz ? */ snprintf(fname, sizeof(fname), "%s%ld.csv", RAIDS_FOLDER, day); strlcpy(tmpfname, SAVE_FOLDER, sizeof(tmpfname)); strlcat(tmpfname, "tmpfile2", sizeof(tmpfname)); @@ -270,8 +335,7 @@ save_to_file(Slayer slayers[], size_t nslayers, char *raid, save_to_new_file(slayers, nslayers, fname, raid); return; } - if ((w = fopen(tmpfname, "w")) == NULL) - die("nolan: Failed to open %s\n", tmpfname); + w = efopen(tmpfname, "w"); while (fgets(line, LINE_SIZE, r)) { endname = strchr(line, DELIM); @@ -280,7 +344,7 @@ save_to_file(Slayer slayers[], size_t nslayers, char *raid, for (i = 0; i < nslayers; i++) { if (!slayers[i].found_in_file) { /* should be strcmp but for common mistakes */ - if (strncmp(slayers[i].name, line, + if (strncasecmp(slayers[i].name, line, strlen(slayers[i].name) - 3) == 0) { slayers[i].found_in_file = 1; break; @@ -290,7 +354,7 @@ save_to_file(Slayer slayers[], size_t nslayers, char *raid, if (i < nslayers) { olddmg = strtoul(endname + 1, NULL, 10); newdmg = olddmg + adjust(slayers[i].damage, raid); - overcap_msg(slayers[i].name, newdmg, client, event); + overcap_msg(slayers[i].name, newdmg, client, channel_id); fprintf(w, "%s%c%lu\n", slayers[i].name, DELIM, newdmg); } else { if (endname) @@ -315,7 +379,8 @@ save_to_file(Slayer slayers[], size_t nslayers, char *raid, void on_raids(struct discord *client, const struct discord_message *event) { - unsigned int i; + int is_png; + unsigned int i, ret; char *txt = NULL, fname[128]; size_t nslayers; Slayer slayers[MAX_SLAYERS]; @@ -325,15 +390,19 @@ on_raids(struct discord *client, const struct discord_message *event) .sync = &chan, }; - if (strcmp(event->attachments->array->content_type, "image/png") == 0) { + is_png = (strcmp(event->attachments->array->content_type, + "image/png") == 0) ? 1 : 0; + if (is_png) snprintf(fname, sizeof(fname), "%s/raids.png", IMAGES_FOLDER); - curl(event->attachments->array->url, fname); - crop(fname, 1); - } else { /* always a jpg, check on_message() */ + else snprintf(fname, sizeof(fname), "%s/raids.jpg", IMAGES_FOLDER); - curl(event->attachments->array->url, fname); - crop(fname, 0); + if ((ret = curl(event->attachments->array->url, fname)) != 0) { + WARN("curl failed CURLcode:%u", ret); + discord_send_message(client, event->channel_id, "Error: \ +Failed to download image <@%lu>.\nFix me <@%lu>", event->author->id, ADMIN); + return; } + crop(fname, is_png); for (i = 0; i < LENGTH(cn_slayer_ids); i++) { if (event->author->id == cn_slayer_ids[i]) { @@ -352,13 +421,22 @@ on_raids(struct discord *client, const struct discord_message *event) if (txt == NULL) txt = ocr(fname, "eng"); - if (txt == NULL || (nslayers = parse(slayers, txt)) == 0) { - emsg(client, event); + if (txt == NULL) { + WARN("failed to read image"); + discord_send_message(client, event->channel_id, "Error: \ +Failed to read image <@%lu>.\nFix me <@%lu>", event->author->id, ADMIN); + return; + } + + nslayers = parse(slayers, txt); + if (nslayers == 0) { + discord_send_message(client, event->channel_id, "This is not \ +a correct screenshot sir <@%lu>.", event->author->id); free(txt); return; } free(txt); discord_get_channel(client, event->channel_id, &rchan); - save_to_file(slayers, nslayers, chan.name, client, event); + save_to_file(slayers, nslayers, chan.name, client, event->channel_id); /* ^ will free slayers[].name */ } diff --git a/src/stats.c b/src/stats.c index 83b33a6..31038ab 100644 --- a/src/stats.c +++ b/src/stats.c @@ -1,11 +1,10 @@ -#include <ctype.h> #include <stdlib.h> #include <string.h> -#include <time.h> +#include <cjson/cJSON.h> #include "nolan.h" -#define LEN(X) (sizeof X - 1) +#define STRLEN(STR) (sizeof STR - 1) static long playtime_to_long(char *playtime, char *str); static long trim_stat(char *str); @@ -93,7 +92,7 @@ playtime_to_str(long playtime) { long days = playtime / 24; long hours = playtime % 24; - size_t siz = 32; + size_t siz = 36; char *buf = emalloc(siz); switch (hours) { @@ -145,96 +144,99 @@ void parse_line(Player *player, char *line) { char *str; + long stat; - if (strncmp(line, "KINGDOM", LEN("KINGDOM")) == 0) { + if (strncmp(line, "KINGDOM", STRLEN("KINGDOM")) == 0) { str = "KINGDOM "; while (*str && (*line++ == *str++)); player->kingdom = line; return; } - if (strncmp(line, "ROYAUME", LEN("ROYAUME")) == 0) { + if (strncmp(line, "ROYAUME", STRLEN("ROYAUME")) == 0) { str = "ROYAUME "; while (*str && (*line++ == *str++)); player->kingdom = line; return; } - if (strncmp(line, "\\ele]]", LEN("\\ele]]")) == 0) { /* tess madness */ + /* tesseract madness */ + if (strncmp(line, "\\ele]]", STRLEN("\\ele]]")) == 0) { str = "\\ele]] "; while (*str && (*line++ == *str++)); player->kingdom = line; return; } - if (strncmp(line, "PLAYTIME", LEN("PLAYTIME")) == 0) { + if (strncmp(line, "PLAYTIME", STRLEN("PLAYTIME")) == 0) { str = "PLAYTIME "; while (*str && (*line++ == *str++)); player->playtime = playtime_to_long(line, "days, "); return; } - if (strncmp(line, "TEMPS DE JEU", LEN("TEMPS DE JEU")) == 0) { + if (strncmp(line, "TEMPS DE JEU", STRLEN("TEMPS DE JEU")) == 0) { str = "TEMPS DE JEU "; while (*str && (*line++ == *str++)); player->playtime = playtime_to_long(line, "jours, "); return; } - if (strncmp(line, "ASCENSION LEVEL", LEN("ASCENSION LEVEL")) == 0 || - strncmp(line, "NIVEAU D'ELEVATION", LEN("NIVEAU D'ELEVATION")) == 0) { + if (strncmp(line, "ASCENSION LEVEL", STRLEN("ASCENSION LEVEL")) == 0 || + strncmp(line, "NIVEAU D'ELEVATION", STRLEN("NIVEAU D'ELEVATION")) == 0) { player->ascension = trim_stat(line); - } else if (strncmp(line, "LEVEL", LEN("LEVEL")) == 0 || - strncmp(line, "NIVEAU", LEN("NIVEAU")) == 0) { - player->level = trim_stat(line); - } else if (strncmp(line, "GLOBAL RANK", LEN("GLOBAL RANK")) == 0 || - strncmp(line, "RANG GLOBAL", LEN("RANG GLOBAL")) == 0) { + } else if (strncmp(line, "LEVEL", STRLEN("LEVEL")) == 0 || + strncmp(line, "NIVEAU", STRLEN("NIVEAU")) == 0) { + if ((stat = trim_stat(line)) <= 250) + player->level = stat; + } else if (strncmp(line, "GLOBAL RANK", STRLEN("GLOBAL RANK")) == 0 || + strncmp(line, "RANG GLOBAL", STRLEN("RANG GLOBAL")) == 0) { player->global = trim_stat(line); - } else if (strncmp(line, "REGIONAL RANK", LEN("REGIONAL RANK")) == 0 || - strncmp(line, "RANG REGIONAL", LEN("RANG REGIONAL")) == 0) { + } else if (strncmp(line, "REGIONAL RANK", STRLEN("REGIONAL RANK")) == 0 || + strncmp(line, "RANG REGIONAL", STRLEN("RANG REGIONAL")) == 0) { player->regional = trim_stat(line); - } else if (strncmp(line, "COMPETITIVE RANK", LEN("COMPETITIVE RANK")) == 0 || - strncmp(line, "RANG COMPETITIF", LEN("RANG COMPETITIF")) == 0) { + } else if (strncmp(line, "COMPETITIVE RANK", STRLEN("COMPETITIVE RANK")) == 0 || + strncmp(line, "RANG COMPETITIF", STRLEN("RANG COMPETITIF")) == 0) { player->competitive = trim_stat(line); - } else if (strncmp(line, "MONSTERS SLAIN", LEN("MONSTERS SLAIN")) == 0 || - strncmp(line, "MONSTRES TUES", LEN("MONSTRES TUES")) == 0) { + } else if (strncmp(line, "MONSTERS SLAIN", STRLEN("MONSTERS SLAIN")) == 0 || + strncmp(line, "MONSTRES TUES", STRLEN("MONSTRES TUES")) == 0) { player->monsters = trim_stat(line); - } else if (strncmp(line, "BOSSES SLAIN", LEN("BOSSES SLAIN")) == 0 || - strncmp(line, "BOSS TUES", LEN("BOSS TUES")) == 0) { + } else if (strncmp(line, "BOSSES SLAIN", STRLEN("BOSSES SLAIN")) == 0 || + strncmp(line, "BOSS TUES", STRLEN("BOSS TUES")) == 0) { player->bosses = trim_stat(line); - } else if (strncmp(line, "PLAYERS DEFEATED", LEN("PLAYERS DEFEATED")) == 0 || - strncmp(line, "JOUEURS VAINCUS", LEN("JOUEURS VAINCUS")) == 0) { + } else if (strncmp(line, "PLAYERS DEFEATED", STRLEN("PLAYERS DEFEATED")) == 0 || + strncmp(line, "JOUEURS VAINCUS", STRLEN("JOUEURS VAINCUS")) == 0) { player->players = trim_stat(line); - } else if (strncmp(line, "QUESTS COMPLETED", LEN("QUESTS COMPLETED")) == 0 || - strncmp(line, "QUETES TERMINEES", LEN("QUETES TERMINEES")) == 0) { + } else if (strncmp(line, "QUESTS COMPLETED", STRLEN("QUESTS COMPLETED")) == 0 || + strncmp(line, "QUETES TERMINEES", STRLEN("QUETES TERMINEES")) == 0) { player->quests = trim_stat(line); - } else if (strncmp(line, "AREAS EXPLORED", LEN("AREAS EXPLORED")) == 0 || - strncmp(line, "TERRES EXPLOREES", LEN("TERRES EXPLOREES")) == 0) { + } else if (strncmp(line, "AREAS EXPLORED", STRLEN("AREAS EXPLORED")) == 0 || + strncmp(line, "TERRES EXPLOREES", STRLEN("TERRES EXPLOREES")) == 0) { player->explored = trim_stat(line); - } else if (strncmp(line, "AREAS TAKEN", LEN("AREAS TAKEN")) == 0 || - strncmp(line, "TERRES PRISES", LEN("TERRES PRISES")) == 0) { + } else if (strncmp(line, "AREAS TAKEN", STRLEN("AREAS TAKEN")) == 0 || + strncmp(line, "TERRES PRISES", STRLEN("TERRES PRISES")) == 0) { player->taken = trim_stat(line); - } else if (strncmp(line, "DUNGEONS CLEARED", LEN("DUNGEONS CLEARED")) == 0 || - strncmp(line, "DONJONS TERMINES", LEN("DONJONS TERMINES")) == 0) { + } else if (strncmp(line, "DUNGEONS CLEARED", STRLEN("DUNGEONS CLEARED")) == 0 || + strncmp(line, "DONJONS TERMINES", STRLEN("DONJONS TERMINES")) == 0) { player->dungeons = trim_stat(line); - } else if (strncmp(line, "COLISEUM WINS", LEN("COLISEUM WINS")) == 0 || - strncmp(line, "VICROIRE DANS LE", LEN("VICTOIRES DANS LE")) == 0 || - strncmp(line, "VICTOIRES DANS LE COLISEE", LEN("VICTOIRES DANS LE COLISEE")) == 0) { + } else if (strncmp(line, "COLISEUM WINS", STRLEN("COLISEUM WINS")) == 0 || + strncmp(line, "VICTOIRES DANS LE", STRLEN("VICTOIRES DANS LE")) == 0 || + strncmp(line, "VICTOIRES DANS LE COLISEE", STRLEN("VICTOIRES DANS LE COLISEE")) == 0) { player->coliseum = trim_stat(line); - } else if (strncmp(line, "ITEMS UPGRADED", LEN("ITEMS UPGRADED")) == 0 || - strncmp(line, "OBJETS AMELIORES", LEN("OBJETS AMELIORES")) == 0) { + } else if (strncmp(line, "ITEMS UPGRADED", STRLEN("ITEMS UPGRADED")) == 0 || + strncmp(line, "OBJETS AMELIORES", STRLEN("OBJETS AMELIORES")) == 0) { player->items = trim_stat(line); - } else if (strncmp(line, "FISH CAUGHT", LEN("FISH CAUGHT")) == 0 || - strncmp(line, "POISSONS ATTRAPES", LEN("POISSONS ATTRAPES")) == 0) { + } else if (strncmp(line, "FISH CAUGHT", STRLEN("FISH CAUGHT")) == 0 || + strncmp(line, "POISSONS ATTRAPES", STRLEN("POISSONS ATTRAPES")) == 0) { player->fish = trim_stat(line); - } else if (strncmp(line, "DISTANCE TRAVELLED", LEN("DISTANCE TRAVELLED")) == 0 || - strncmp(line, "DISTANCE VOYAGEE", LEN("DISTANCE VOYAGEE")) == 0) { + } else if (strncmp(line, "DISTANCE TRAVELLED", STRLEN("DISTANCE TRAVELLED")) == 0 || + strncmp(line, "DISTANCE VOYAGEE", STRLEN("DISTANCE VOYAGEE")) == 0) { player->distance = trim_stat(line); - } else if (strncmp(line, "REPUTATION", LEN("REPUTATION")) == 0) { + } else if (strncmp(line, "REPUTATION", STRLEN("REPUTATION")) == 0) { player->reputation = trim_stat(line); - } else if (strncmp(line, "ENDLESS RECORD", LEN("ENDLESS RECORD")) == 0 || - strncmp(line, "RECORD DU MODE", LEN("RECORD DU MODE")) == 0 || - strncmp(line, "RECORD DU MODE SANS-FIN", LEN("RECORD DU MODE SANS-FIN")) == 0) { + } else if (strncmp(line, "ENDLESS RECORD", STRLEN("ENDLESS RECORD")) == 0 || + strncmp(line, "RECORD DU MODE", STRLEN("RECORD DU MODE")) == 0 || + strncmp(line, "RECORD DU MODE SANS-FIN", STRLEN("RECORD DU MODE SANS-FIN")) == 0) { player->endless = trim_stat(line); - } else if (strncmp(line, "ENTRIES COMPLETED", LEN("ENTRIES COMPLETED")) == 0 || - strncmp(line, "RECHERCHES TERMINEES", LEN("RECHERCHES TERMINEES")) == 0) { + } else if (strncmp(line, "ENTRIES COMPLETED", STRLEN("ENTRIES COMPLETED")) == 0 || + strncmp(line, "RECHERCHES TERMINEES", STRLEN("RECHERCHES TERMINEES")) == 0) { player->codex = trim_stat(line); } } @@ -262,7 +264,7 @@ create_player(Player *player, unsigned int i) if (player->name) players[i].name = strndup(player->name, MAX_USERNAME_LEN); else - players[i].name = strdup("placeholder"); + players[i].name = strdup("placeholder"); /* FIXME */ players[i].kingdom = strndup(player->kingdom, MAX_KINGDOM_LEN); for (j = 2; j < LENGTH(fields); j++) ((long *)&players[i])[j] = ((long *)player)[j]; @@ -295,8 +297,6 @@ update_player(char *buf, int siz, Player *player, unsigned int i) /* -2 to not include update and userid */ for (j = 2; j < LENGTH(fields) - 2; j++) { - if (siz <= 0) - warn("nolan: string truncation in %s\n", __func__); old = ((long *)&players[i])[j]; new = ((long *)player)[j]; diff = new - old; @@ -328,9 +328,10 @@ update_player(char *buf, int siz, Player *player, unsigned int i) /* update player */ ((long *)&players[i])[j] = new; } - + if (siz <= 0) + WARN("string truncation"); if (!strftime(p, siz, "\nLast update was on %d %b %Y at %R UTC\n", tm)) - warn("nolan: strftime: string truncation in %s\n", __func__); + WARN("strftime: string truncation"); players[i].update = player->update; /* @@ -351,10 +352,8 @@ update_file(Player *player) strlcpy(tmpfname, SAVE_FOLDER, sizeof(tmpfname)); strlcat(tmpfname, "tmpfile", sizeof(tmpfname)); - if ((r = fopen(STATS_FILE, "r")) == NULL) - die("nolan: Failed to open %s\n", STATS_FILE); - if ((w = fopen(tmpfname, "w")) == NULL) - die("nolan: Failed to open %s\n", tmpfname); + r = efopen(STATS_FILE, "r"); + w = efopen(tmpfname, "w"); while (fgets(line, LINE_SIZE, r)) { endname = strchr(line, DELIM); @@ -400,8 +399,7 @@ update_players(char *buf, size_t siz, Player *player) if (i == nplayers) { /* new player */ nplayers++; if (nplayers > MAX_PLAYERS) - die("nolan: There is too much players (max:%d)\n", - MAX_PLAYERS); + DIE("there is too much players (max:%lu)", MAX_PLAYERS); create_player(player, i); r = snprintf(buf, siz, "**%s** has been registrated in the database.\n\n", @@ -420,28 +418,27 @@ void stats(char *buf, size_t siz, char *url, char *username, u64snowflake userid, u64snowflake guild_id, struct discord *client) { - unsigned int i; - char *txt, fname[128], lower_username[MAX_USERNAME_LEN]; + unsigned int i, ret; + char *txt, fname[128]; Player player; /* not always a jpg but idc */ snprintf(fname, sizeof(fname), "%s/%lu.jpg", IMAGES_FOLDER, userid); - curl(url, fname); - txt = ocr(fname, "eng"); - if (txt == NULL) { - warn("nolan: Failed to read stats image\n"); + if ((ret = curl(url, fname)) != 0) { + WARN("curl failed CURLcode:%u", ret); + strlcpy(buf, "Error: Failed to download image", siz); + return; + } + if ((txt = ocr(fname, "eng")) == NULL) { + WARN("failed to read image"); strlcpy(buf, "Error: Failed to read image", siz); return; } memset(&player, 0, sizeof(player)); - if (username) { - for (i = 0; i < strlen(username); i++) - lower_username[i] = tolower(username[i]); - lower_username[i] = '\0'; - player.name = lower_username; - } + if (username) + player.name = username; player.userid = userid; player.update = time(NULL); for_line(&player, txt); @@ -500,20 +497,22 @@ on_stats_interaction(struct discord *client, const struct discord_interaction *event) { char buf[MAX_MESSAGE_LEN] = ""; + const cJSON *attachment = NULL, *url = NULL; + cJSON *attachments = cJSON_Parse(event->data->resolved->attachments); - if (!event->data->resolved->attachments->array[0].url) { - strlcpy(buf, "Error: For some reason the image was not loaded \ -correctly", sizeof(buf)); - } else { - stats(buf, - sizeof(buf), - event->data->resolved->attachments->array[0].url, - event->member->user->username, - event->member->user->id, - event->guild_id, - client); + cJSON_ArrayForEach(attachment, attachments) { + url = cJSON_GetObjectItemCaseSensitive(attachment, "url"); } + stats(buf, + sizeof(buf), + url->valuestring, + event->member->user->username, + event->member->user->id, + event->guild_id, + client); + cJSON_Delete(attachments); + struct discord_interaction_response params = { .type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE, .data = &(struct discord_interaction_callback_data) @@ -531,26 +530,27 @@ on_stats_admin_interaction(struct discord *client, const struct discord_interaction *event) { char buf[MAX_MESSAGE_LEN] = ""; + const cJSON *attachment = NULL, *url = NULL; + cJSON *attachments = cJSON_Parse(event->data->resolved->attachments); u64snowflake userid = str_to_uid(event->data->options->array[1].value); - fprintf(stderr, "%s\n", event->data->resolved->attachments->array[0].url); - fprintf(stderr, "%s\n", event->data->options->array[1].value); + cJSON_ArrayForEach(attachment, attachments) { + url = cJSON_GetObjectItemCaseSensitive(attachment, "url"); + } - if (!event->data->resolved->attachments->array[0].url) { - strlcpy(buf, "Error: For some reason the image was not loaded \ -correctly", sizeof(buf)); - } else if (!userid) { + if (!userid) { strlcpy(buf, "Error: This is probably not a correct user", sizeof(buf)); } else { stats(buf, sizeof(buf), - event->data->resolved->attachments->array[0].url, + url->valuestring, NULL, userid, event->guild_id, client); } + cJSON_Delete(attachments); struct discord_interaction_response params = { .type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE, @@ -1,33 +1,8 @@ -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> #include <string.h> #include <sys/stat.h> #include "util.h" -void -warn(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); -} - -void -die(const char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - exit(EXIT_FAILURE); -} - char * nstrchr(const char *s, int c, int n) { @@ -69,7 +44,7 @@ strlcat(char *dst, const char *src, size_t siz) } int -file_exists(char *filename) +file_exists(const char *filename) { struct stat buf; return (stat(filename, &buf) == 0); @@ -81,16 +56,16 @@ emalloc(size_t size) void *p; if ((p = malloc(size)) == NULL) - die("malloc failed\n"); + DIE("malloc failed"); return p; } -void * -ecalloc(size_t nmemb, size_t size) +FILE * +efopen(const char *filename, const char *modes) { - void *p; + FILE *fp; - if ((p = calloc(nmemb, size)) == NULL) - die("calloc failed\n"); - return p; + if ((fp = fopen(filename, modes)) == NULL) + DIE("failed to open %s (%s)", filename, modes); + return fp; } @@ -1,12 +1,28 @@ -#define LENGTH(X) (sizeof(X) / sizeof(X[0])) -#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) -#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#ifndef UTIL_H +#define UTIL_H + +#include <stdio.h> +#include <stdlib.h> + +#define LENGTH(X) (sizeof(X) / sizeof(X[0])) +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#define __EPRINTF(...) ((fprintf(stderr,\ + "\033[1m%s:%d: %s: ",\ + __FILE__,\ + __LINE__,\ + __func__)),\ + (fprintf(stderr, __VA_ARGS__)),\ + (fprintf(stderr, "\033[m\n"))) +#define WARN(...) (__EPRINTF("\033[35;1mwarning:\033[39;1m " __VA_ARGS__)) +#define DIE(...) (__EPRINTF("\033[31;1merror:\033[39;1m " __VA_ARGS__),\ + exit(EXIT_FAILURE)) -void warn(const char *fmt, ...); -void die(const char *fmt, ...); char *nstrchr(const char *s, int c, int n); size_t strlcpy(char *dst, const char *src, size_t siz); size_t strlcat(char *dst, const char *src, size_t siz); -int file_exists(char *filename); +int file_exists(const char *filename); void *emalloc(size_t size); -void *ecalloc(size_t nmemb, size_t size); +FILE *efopen(const char *filename, const char *modes); + +#endif /* UTIL_H */ |