#include #include #include #include #include #include #include #include "cJSON.h" #include "fetch.h" #include "image.h" #define UNUSED(x) (void)x #define OMDB_URL "http://www.omdbapi.com" #define PROJECT_NAME "omdb" const char *TITLE_LOOKUP_KEYS[] = { "Actors", "Awards", "BoxOffice", "Country", "DVD", "Director", "Genre", "Language", "Metascore", "Plot", "Production", "Rated", "Ratings", "Released", "Response", "Runtime", "Title", "Type", "Website", "Writer", "Year", "imdbID", "imdbRating", "imdbVotes", NULL}; /* Default to plain output. */ static enum _output_format { OUTPUT_JSON, OUTPUT_PLAIN, OUTPUT_FANCY } output_format = OUTPUT_FANCY; static struct option opts[] = {{"api-key", required_argument, NULL, 'k'}, {"id", no_argument, NULL, 'i'}, {"json", no_argument, NULL, 'j'}, {"full-plot", no_argument, NULL, 'f'}, {"search-type", required_argument, NULL, 't'}, {"year", required_argument, NULL, 'y'}, {NULL, 0, NULL, 0}}; void print_usage(void) { printf( PROJECT_NAME " \n" "\t-i/--id: Search by ID rather than title. (optional)\n" "\t-j/--json: Output full JSON response.\n" "\t-k/--api-key: OMDB API Key. (required)\n" "\t-f/--full-plot: Output long plot description.\n" "\t-t/--search-type: Search type. (optional) [movie, series, episode]\n" "\t-y/--year: Year of release.\n"); } /* Print entire JSON response as is. */ int print_title_json(const cJSON *const json) { char *json_str = cJSON_Print(json); if (!json_str) { fputs("Couldn't output formatted JSON.\n", stderr); return -1; } fputs(json_str, stdout); putchar('\n'); free(json_str); return 0; } /* Return 0 if all required fields for a title / id check are present. */ int verify_title_fields(const cJSON *restrict json) { const char **key = TITLE_LOOKUP_KEYS; while (*key != NULL) { if (cJSON_GetObjectItem(json, *key) == NULL) { fprintf(stderr, "Missing field '%s' in JSON.\n", *key); return -1; } key++; } return 0; } int print_ratings(const cJSON *restrict ratings_obj) { cJSON *rating = NULL; if ((ratings_obj == NULL) || !cJSON_IsArray(ratings_obj)) { fputs("Ratings either doesn't exist, or can't be parsed.", stderr); return -1; } cJSON_ArrayForEach(rating, ratings_obj) { cJSON *r_source = cJSON_GetObjectItem(rating, "Source"); cJSON *r_rating = cJSON_GetObjectItem(rating, "Value"); if ((r_source == NULL) || (r_rating == NULL)) continue; fprintf(stderr, "Rated %s by %s\n", r_rating->valuestring, r_source->valuestring); } return 0; } /* Print some title information in plain text. */ int print_title_plain(const cJSON *restrict json) { if (verify_title_fields(json) != 0) return -1; cJSON *title = cJSON_GetObjectItem(json, "Title"); cJSON *rated = cJSON_GetObjectItem(json, "Rated"); cJSON *released = cJSON_GetObjectItem(json, "Released"); printf("%s [%s]\n", title->valuestring, rated->valuestring); printf("Released on %s\n", released->valuestring); print_ratings(cJSON_GetObjectItem(json, "Ratings")); cJSON *plot = cJSON_GetObjectItem(json, "Plot"); printf("\n%s\n", plot->valuestring); cJSON *actors = cJSON_GetObjectItem(json, "Actors"); printf("\nStarring %s\n", actors->valuestring); return 0; } int print_title_fancy(const cJSON *restrict json) { /* Bad looking, but attempts to fetch, and show the poster if possible. */ cJSON *poster = cJSON_GetObjectItem(json, "Poster"); if (poster != NULL) { struct fetch *f = fetch_init(); CURLU *url = init_curl_url(poster->valuestring); if (url != NULL) { struct response *resp = fetch(f, url); if (resp != NULL) { process_image_mem((const unsigned char *)resp->data, resp->size); response_cleanup(resp); fetch_cleanup(f); } } } return print_title_plain(json); } int handle_response(struct response *restrict resp) { cJSON *json = NULL; if (strstr(resp->content_type, "application/json") == NULL) { fprintf(stderr, "Unexpected response: %s\n", resp->content_type); return -1; } json = cJSON_ParseWithLength(resp->data, resp->size); if (json == NULL) { const char *error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { fprintf(stderr, "Error before: %s\n", error_ptr); } return -1; } switch (output_format) { case OUTPUT_PLAIN: print_title_plain(json); break; case OUTPUT_JSON: print_title_json(json); break; case OUTPUT_FANCY: /* Just print_title + image planned here for now */ print_title_fancy(json); break; } cJSON_free(json); return 0; } int main(int argc, char **argv) { int ch; bool k_flag = false; char search_mode[8] = "movie"; if (argc == 0) { fputs("Argument required.\n", stderr); print_usage(); exit(EXIT_FAILURE); } /* Just validate and add options directly to the request now. */ CURLU *url = init_curl_url(OMDB_URL); if (url == NULL) { fputs("Can't set url for retrieval.\n", stderr); exit(EXIT_FAILURE); } while ((ch = getopt_long(argc, argv, "hijk:ft:y:", opts, NULL)) != -1) { switch (ch) { case 'i': /* Lookup by ID */ set_param(url, "id", optarg); break; case 'j': output_format = OUTPUT_JSON; break; case 'k': /* API Key */ k_flag = true; set_param(url, "apikey", optarg); break; case 'f': set_param(url, "plot", "full"); break; case 't': /* Type: movie, series, episode */ strlcpy(search_mode, optarg, sizeof search_mode); break; case 'y': set_param(url, "year", optarg); break; case '?': fprintf(stderr, "Invalid option'%c'.\n", optopt); exit(EXIT_FAILURE); case 'h': default: print_usage(); exit(EXIT_FAILURE); } } argc -= optind; if (!k_flag) { fputs("Missing required arguments.\n", stderr); exit(EXIT_FAILURE); } // printf("Looking for: %s\n", argv[optind]); /* Set remaining params if any... */ set_param(url, "type", search_mode); set_param(url, "r", "json"); /* Search for title... */ set_param(url, "t", argv[optind]); /* Print it... */ // print_url(url); /* * All of the call setup should be done via the URL so all that's left is to * send the request and conditionally handle any response. */ struct fetch *f = fetch_init(); /* Check this */ struct response *resp = fetch(f, url); handle_response(resp); /* process_image("./test.jpg"); */ free(resp->data); free(resp); curl_url_cleanup(url); fetch_cleanup(f); return 0; }