From b2e4168ece2e6f378e06d3833c179299e82254fc Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 15 Jul 2020 20:36:51 +0200 Subject: Add the ability to add rss from episode name --- build.sh | 2 +- src/download.c | 12 +++++ src/download.h | 1 + src/episode.c | 133 +++++++++++++++++++++++++++++++++++++++++++++++ src/episode.h | 19 +++++++ src/rss.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 322 insertions(+), 5 deletions(-) create mode 100644 src/episode.c create mode 100644 src/episode.h diff --git a/build.sh b/build.sh index 9ecbe97..3acb657 100755 --- a/build.sh +++ b/build.sh @@ -2,4 +2,4 @@ CFLAGS="-O3 -s -flto" [ -z "$RELEASE" ] && CFLAGS="-O0 -g3 -Wall -Wextra -Werror"; -musl-gcc -static src/main.c src/program.c src/alloc.c src/buffer.c src/fileutils.c src/transmission.c src/rss.c src/html.c src/rss_html_common.c src/download.c src/stringutils.c -o automedia $CFLAGS +musl-gcc -static src/main.c src/program.c src/alloc.c src/buffer.c src/fileutils.c src/transmission.c src/rss.c src/html.c src/rss_html_common.c src/download.c src/stringutils.c src/episode.c -o automedia $CFLAGS diff --git a/src/download.c b/src/download.c index 74348e5..4154dcb 100644 --- a/src/download.c +++ b/src/download.c @@ -18,3 +18,15 @@ int download_to_buffer(const char *url, Buffer *buffer) { buffer_append(buffer, "\0", 1); return result; } + +int is_header_response_ok(const char *url) { + const char *args[] = { + "curl", "-s", "-L", "-f", "-I", + "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", + "-H", "Accept-Language: en-US,en;q=0.5", + "--", + url, + NULL + }; + return program_exec(args, NULL, NULL); +} diff --git a/src/download.h b/src/download.h index 0682ba9..3c7f0aa 100644 --- a/src/download.h +++ b/src/download.h @@ -3,5 +3,6 @@ struct Buffer; int download_to_buffer(const char *url, struct Buffer *buffer); +int is_header_response_ok(const char *url); #endif diff --git a/src/episode.c b/src/episode.c new file mode 100644 index 0000000..e89195c --- /dev/null +++ b/src/episode.c @@ -0,0 +1,133 @@ +#include "episode.h" +#include "stringutils.h" +#include +#include + +static int string_find(const char *str, int len, int offset, char char_to_find) { + for(int i = offset; i < len; ++i) { + if(str[i] == char_to_find) + return i; + } + return -1; +} + +static int string_rfind(const char *str, int len, int offset, char char_to_find) { + for(int i = offset - 1; i >= 0; --i) { + if(str[i] == char_to_find) + return i + (len - offset); + } + return -1; +} + +int episode_info_create_from_episode_name(EpisodeInfo *self, const char *episode_name) { + self->episode_name[0] = '\0'; + self->group_name = NULL; + self->anime_name = NULL; + self->episode = NULL; + self->resolution = NULL; + self->extension = NULL; + + int episode_name_len = strlen(episode_name); + if(episode_name_len > 500) { + fprintf(stderr, "Episode name is too long! it can't be more than 500 characters\n"); + return -1; + } + + char episode_name_s[512]; + strcpy(episode_name_s, episode_name); + char *episode_name_stripped = strip(episode_name_s); + int episode_name_stripped_len = strlen(episode_name_stripped); + int episode_name_offset = 0; + + if(episode_name_stripped_len == 0) + return -1; + + int extension_index = string_rfind(episode_name_stripped, episode_name_stripped_len, episode_name_stripped_len, '.'); + if(extension_index != -1) { + int extension_len = episode_name_stripped_len - extension_index; + self->extension = self->episode_name + episode_name_offset; + memcpy(self->extension, episode_name_stripped + extension_index, extension_len); + self->extension[extension_len] = '\0'; + episode_name_offset += extension_len + 1; + episode_name_stripped_len = extension_index; + } + + if(episode_name_stripped[0] != '[') + return -1; + + int group_name_end = string_find(episode_name_stripped, episode_name_stripped_len, 0, ']'); + if(group_name_end == -1) + return -1; + + int group_name_len = group_name_end - 1; + self->group_name = self->episode_name + episode_name_offset; + memcpy(self->group_name, episode_name_stripped + 1, group_name_len); + self->group_name[group_name_len] = '\0'; + self->group_name = strip(self->group_name); + episode_name_offset += group_name_len + 1; + + int last_dash = string_rfind(episode_name_stripped, episode_name_stripped_len, episode_name_stripped_len, '-'); + if(last_dash == -1) + return -1; + + int anime_name_len = last_dash - 1 - group_name_end; + self->anime_name = self->episode_name + episode_name_offset; + memcpy(self->anime_name, episode_name_stripped + group_name_end + 1, anime_name_len); + self->anime_name[anime_name_len] = '\0'; + self->anime_name = strip(self->anime_name); + episode_name_offset += anime_name_len + 1; + + int resolution_index = string_find(episode_name_stripped, episode_name_stripped_len, last_dash + 1, '['); + if(resolution_index == -1) { + /* TODO: Test if this works */ + int episode_len = episode_name_stripped_len - (last_dash + 1); + self->episode = self->episode_name + episode_name_offset; + memcpy(self->episode, episode_name_stripped + last_dash + 1, episode_len); + self->episode[episode_len] = '\0'; + self->episode = strip(self->episode); + episode_name_offset += episode_len + 1; + } else { + int episode_len = resolution_index - (last_dash + 1); + self->episode = self->episode_name + episode_name_offset; + memcpy(self->episode, episode_name_stripped + last_dash + 1, episode_len); + self->episode[episode_len] = '\0'; + self->episode = strip(self->episode); + episode_name_offset += episode_len + 1; + + int resolution_end = string_find(episode_name_stripped, episode_name_stripped_len, resolution_index + 1, ']'); + if(resolution_end != -1) { + int resolution_len = resolution_end - (resolution_index + 1); + self->resolution = self->episode_name + episode_name_offset; + memcpy(self->resolution, episode_name_stripped + resolution_index + 1, resolution_len); + self->resolution[resolution_len] = '\0'; + self->resolution = strip(self->resolution); + } + } + + return 0; +} + +int episode_info_get_generic_name(EpisodeInfo *self, char *output_buffer, int output_buffer_size) { + if(!self->group_name || !self->anime_name) + return -1; + + int bytes_written = 0; + if(self->resolution) { + if(self->extension) + bytes_written = snprintf(output_buffer, output_buffer_size, "[%s] %s [%s]%s", self->group_name, self->anime_name, self->resolution, self->extension); + else + bytes_written = snprintf(output_buffer, output_buffer_size, "[%s] %s [%s]", self->group_name, self->anime_name, self->resolution); + } else { + if(self->extension) + bytes_written = snprintf(output_buffer, output_buffer_size, "[%s] %s%s", self->group_name, self->anime_name, self->extension); + else + bytes_written = snprintf(output_buffer, output_buffer_size, "[%s] %s", self->group_name, self->anime_name); + } + + if(bytes_written >= output_buffer_size) { + fprintf(stderr, "Failed to write generic name to buffer\n"); + return -1; + } + + return 0; +} diff --git a/src/episode.h b/src/episode.h new file mode 100644 index 0000000..66c4e8a --- /dev/null +++ b/src/episode.h @@ -0,0 +1,19 @@ +#ifndef EPISODE_H +#define EPISODE_H + +typedef struct { + char episode_name[512]; + + /* These are pointers inside @episode_name */ + char *group_name; + char *anime_name; + char *episode; + char *resolution; + char *extension; +} EpisodeInfo; + +/* Fails if @episode_name is more than 500 characters long or if group or anime name is missing/cant be parsed */ +int episode_info_create_from_episode_name(EpisodeInfo *self, const char *episode_name); +int episode_info_get_generic_name(EpisodeInfo *self, char *output_buffer, int output_buffer_size); + +#endif diff --git a/src/rss.c b/src/rss.c index 442fb58..9486e6e 100644 --- a/src/rss.c +++ b/src/rss.c @@ -4,6 +4,7 @@ #include "stringutils.h" #include "fileutils.h" #include "buffer.h" +#include "episode.h" #include "rss_html_common.h" #include "json.h" #include "alloc.h" @@ -165,6 +166,139 @@ static int rss_parse_add_callback(char *title, char *link, void *userdata) { return 0; } +static const char hex_characters[] = "0123456789ABCDEF"; +/* TODO: Also support unicode escape characters */ +static void url_escape(const char *str, char *output) { + int index = 0; + for(;;) { + char c = *str; + if(c == '\0') { + output[index] = '\0'; + break; + } else if(c < 32) { + output[index++] = '%'; + output[index++] = hex_characters[(c>>4) & 0x0F]; + output[index++] = hex_characters[c&0x0F]; + } else { + switch(c) { + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '/': + case ':': + case ';': + case '=': + case '?': + case '@': + case '[': + case ']': + case '"': + case '-': + case '.': + case '<': + case '>': + case '\\': + case '^': + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + case ' ': + output[index++] = '%'; + output[index++] = hex_characters[(c>>4) & 0x0F]; + output[index++] = hex_characters[c&0x0F]; + break; + default: + output[index++] = c; + break; + } + } + ++str; + } +} + +static int get_rss_url_from_episode_info(const char *episode_name, EpisodeInfo *episode_info, char *rss_url) { + char *selected_submitter = NULL; + char response[512]; + char group_name_escaped[1536]; + + for(;;) { + printf("Enter the name of the submitter (leave empty to choose \"%s\" or type \"anon\" to choose all submitters: ", episode_info->group_name); + if(!fgets(response, sizeof(response), stdin)) { + fprintf(stderr, "Failed to read response from stdin\n"); + return -1; + } + fprintf(stderr, "response: %s\n", response); + char *response_str = strip(response); + + if(strcmp(response, "anon") == 0) { + break; + } else { + if(strlen(response_str) == 0) + response_str = episode_info->group_name; + + url_escape(response, group_name_escaped); + char url[4096]; + if(snprintf(url, sizeof(url), "https://nyaa.si/user/%s", group_name_escaped) >= (int)sizeof(url)) { + fprintf(stderr, "Error: url is too long!\n"); + return -1; + } + + if(is_header_response_ok(url) == 0) { + selected_submitter = response_str; + break; + } else { + printf("The submitter \"%s\" doesn't exist on nyaa.si, please choose another submitter.\n", response_str); + } + } + } + + char generic_name[2048]; + if(episode_info_get_generic_name(episode_info, generic_name, sizeof(generic_name)) != 0) { + fprintf(stderr, "Failed to get name for episode!\n"); + return -1; + } + + for(;;) { + if(selected_submitter) + printf("Are you sure you want to track \"%s\" by %s after \"%s\" ? (Y)es/No: ", generic_name, selected_submitter, episode_name); + else + printf("Are you sure you want to track \"%s\" by all submitters after \"%s\" ? (Y)es/No: ", generic_name, episode_name); + + char sresp[128]; + if(!fgets(sresp, sizeof(sresp), stdin)) { + fprintf(stderr, "Failed to read response from stdin\n"); + return -1; + } + char *response_str = strip(response); + int response_len = strlen(response_str); + + if(response_len > 0 && (response_str[0] == 'n' || response_str[0] == 'N')) { + rss_url[0] = '\0'; + return 0; + } else if(response_len == 0 || response_str[0] == 'y' || response_str[0] == 'Y') { + break; + } + } + + if(selected_submitter) + sprintf(rss_url, "https://nyaa.si/?page=rss&q=%s&c=0_0&f=0&u=%s", group_name_escaped, selected_submitter); + else + sprintf(rss_url, "https://nyaa.si/?page=rss&q=%s&c=0_0&f=0", group_name_escaped); + + return 0; +} + int add_rss(const char *name, const char *url, char *rss_config_dir, const char *start_after) { int result = 0; @@ -172,8 +306,28 @@ int add_rss(const char *name, const char *url, char *rss_config_dir, const char buffer_init(&buffer); result = download_to_buffer(url, &buffer); if(result != 0) { - fprintf(stderr, "Failed to download rss: %s\n", url); - goto cleanup; + EpisodeInfo episode_info; + if(episode_info_create_from_episode_name(&episode_info, url) != 0) { + fprintf(stderr, "Failed to download rss: %s\n", url); + goto cleanup; + } + + char rss_url[4096]; + if(get_rss_url_from_episode_info(url, &episode_info, rss_url) != 0) + goto cleanup; + + /* User didn't want to track rss */ + if(rss_url[0] == '\0') { + result = 0; + goto cleanup; + } + + buffer_clear(&buffer); + result = download_to_buffer(url, &buffer); + if(result != 0) { + fprintf(stderr, "Failed to download rss: %s\n", url); + goto cleanup; + } } RssParseUserdata rss_parse_userdata; @@ -194,8 +348,6 @@ int add_rss(const char *name, const char *url, char *rss_config_dir, const char goto cleanup; } - /* TODO: Add (add rss ) here */ - if(!name) { if(!rss_title) { fprintf(stderr, "Failed to find rss title and --name was not provided\n"); -- cgit v1.2.3