omdb/omdb.c

275 lines
6.9 KiB
C
Raw Normal View History

2025-12-13 06:22:27 +00:00
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>
#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"
2025-12-15 08:15:12 +00:00
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};
2025-12-15 08:15:12 +00:00
2025-12-13 06:22:27 +00:00
/* Default to plain output. */
static enum _output_format {
OUTPUT_JSON,
OUTPUT_PLAIN,
OUTPUT_FANCY
} output_format = OUTPUT_FANCY;
2025-12-13 06:22:27 +00:00
2025-12-15 08:15:12 +00:00
static struct option opts[] = {{"api-key", required_argument, NULL, 'k'},
{"id", no_argument, NULL, 'i'},
2025-12-13 06:22:27 +00:00
{"json", no_argument, NULL, 'j'},
2025-12-15 08:15:12 +00:00
{"full-plot", no_argument, NULL, 'f'},
2025-12-13 06:22:27 +00:00
{"search-type", required_argument, NULL, 't'},
2025-12-15 08:15:12 +00:00
{"year", required_argument, NULL, 'y'},
2025-12-13 06:22:27 +00:00
{NULL, 0, NULL, 0}};
void print_usage(void) {
printf(
PROJECT_NAME
2025-12-15 08:15:12 +00:00
" <OPTIONS> <TITLE-OR-ID>\n"
2025-12-13 06:22:27 +00:00
"\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"
2025-12-15 08:15:12 +00:00
"\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");
}
2025-12-13 06:22:27 +00:00
/* 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;
}
2025-12-15 08:15:12 +00:00
/* 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) {
2025-12-15 08:15:12 +00:00
if (cJSON_GetObjectItem(json, *key) == NULL) {
fprintf(stderr, "Missing field '%s' in JSON.\n", *key);
return -1;
}
2025-12-13 06:22:27 +00:00
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);
}
2025-12-15 08:15:12 +00:00
return 0;
}
/* Print some title information in plain text. */
int print_title_plain(const cJSON *restrict json) {
if (verify_title_fields(json) != 0)
2025-12-13 06:22:27 +00:00
return -1;
2025-12-15 08:15:12 +00:00
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);
2025-12-13 06:22:27 +00:00
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);
2025-12-13 06:22:27 +00:00
return 0;
}
2025-12-15 08:15:12 +00:00
int print_title_fancy(const cJSON *restrict json) {
/* Bad looking, but attempts to fetch, and show the poster if possible. */
2025-12-15 08:15:12 +00:00
cJSON *poster = cJSON_GetObjectItem(json, "Poster");
if (poster != NULL) {
2025-12-15 08:15:12 +00:00
struct fetch *f = fetch_init();
CURLU *url = init_curl_url(poster->valuestring);
if (url != NULL) {
struct response *resp = fetch(f, url);
2025-12-15 08:15:12 +00:00
if (resp != NULL) {
process_image_mem((const unsigned char *)resp->data, resp->size);
response_cleanup(resp);
fetch_cleanup(f);
}
}
}
2025-12-15 08:15:12 +00:00
return print_title_plain(json);
}
2025-12-13 06:22:27 +00:00
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);
2025-12-13 06:22:27 +00:00
break;
}
cJSON_free(json);
return 0;
}
int main(int argc, char **argv) {
int ch;
2026-01-09 10:52:45 +00:00
bool i_flag = false;
2025-12-13 06:22:27 +00:00
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. */
2025-12-15 08:15:12 +00:00
CURLU *url = init_curl_url(OMDB_URL);
2025-12-13 06:22:27 +00:00
if (url == NULL) {
2025-12-15 08:15:12 +00:00
fputs("Can't set url for retrieval.\n", stderr);
2025-12-13 06:22:27 +00:00
exit(EXIT_FAILURE);
}
2025-12-13 06:22:27 +00:00
2026-01-09 10:52:45 +00:00
while ((ch = getopt_long(argc, argv, "hi:jk:ft:y:", opts, NULL)) != -1) {
2025-12-13 06:22:27 +00:00
switch (ch) {
case 'i':
/* Lookup by ID */
2026-01-09 10:52:45 +00:00
i_flag = true;
set_param(url, "i", optarg);
2025-12-13 06:22:27 +00:00
break;
case 'j':
output_format = OUTPUT_JSON;
break;
case 'k':
/* API Key */
k_flag = true;
set_param(url, "apikey", optarg);
break;
2025-12-15 08:15:12 +00:00
case 'f':
set_param(url, "plot", "full");
break;
2025-12-13 06:22:27 +00:00
case 't':
/* Type: movie, series, episode */
strlcpy(search_mode, optarg, sizeof search_mode);
break;
2025-12-15 08:15:12 +00:00
case 'y':
set_param(url, "year", optarg);
break;
2025-12-13 06:22:27 +00:00
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);
}
/* Set remaining params if any... */
set_param(url, "type", search_mode);
set_param(url, "r", "json");
/* Search for title... */
2026-01-09 10:52:45 +00:00
if (i_flag == false)
set_param(url, "t", argv[optind]);
2025-12-13 06:22:27 +00:00
/* Print it... */
2026-01-09 10:52:45 +00:00
/* print_url(url); */
2025-12-13 06:22:27 +00:00
/*
* 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);
2025-12-15 08:15:12 +00:00
/* process_image("./test.jpg"); */
2025-12-13 06:22:27 +00:00
free(resp->data);
free(resp);
curl_url_cleanup(url);
fetch_cleanup(f);
return 0;
}