From 51e7fcaad277207e6327f9d95c2aa074e714c66d Mon Sep 17 00:00:00 2001 From: stab Date: Sun, 22 Mar 2026 22:24:18 -0500 Subject: [PATCH 01/61] Added favicon to search results --- src/Config.h | 2 +- src/Routes/Search.c | 28 ++++++++++++++++++++++++++++ static/main.css | 12 ++++++++++++ templates/results.html | 11 ++++++++--- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Config.h b/src/Config.h index 4143bbd..67882d1 100644 --- a/src/Config.h +++ b/src/Config.h @@ -20,7 +20,7 @@ #define MD5_HASH_LEN 32 #define HEX_CHARS "0123456789abcdef" -#define INFOBOX_FIELD_COUNT 4 +#define INFOBOX_FIELD_COUNT 5 #define MAX_RESULTS_PER_ENGINE 10 #define CURL_TIMEOUT_SECS 15L diff --git a/src/Routes/Search.c b/src/Routes/Search.c index bc35fb6..b75f398 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -53,7 +53,31 @@ static InfoBox fetch_unit_wrapper(char *query) { static InfoBox fetch_currency_wrapper(char *query) { return fetch_currency_data(query); } +char *get_base_url(const char *input) { + if (!input) return NULL; + const char *start = input; + + const char *protocol_pos = strstr(input, "://"); + if (protocol_pos) { + start = protocol_pos + 3; + } + + const char *end = start; + while (*end && *end != '/' && *end != '?' && *end != '#') { + end++; + } + + size_t len = end - start; + + char *domain = (char *)malloc(len + 1); + if (!domain) return NULL; + + strncpy(domain, start, len); + domain[len] = '\0'; + + return domain; +} static int is_calculator_query(const char *query) { if (!query) return 0; @@ -150,6 +174,7 @@ static int add_infobox_to_collection(InfoBox *infobox, char ****collection, (*collection)[current_count][2] = infobox->extract ? strdup(infobox->extract) : NULL; (*collection)[current_count][3] = infobox->url ? strdup(infobox->url) : NULL; + (*collection)[current_count][4] = infobox->url ? strdup(infobox->url) : NULL; (*inner_counts)[current_count] = INFOBOX_FIELD_COUNT; return current_count + 1; @@ -490,6 +515,7 @@ int results_handler(UrlParams *params) { continue; } char *pretty_url = pretty_display_url(display_url); + char *base_url = get_base_url(display_url); results_matrix[unique_count][0] = strdup(display_url); results_matrix[unique_count][1] = strdup(pretty_url); @@ -499,10 +525,12 @@ int results_handler(UrlParams *params) { results_matrix[unique_count][3] = all_results[i][j].snippet ? strdup(all_results[i][j].snippet) : strdup(""); + results_matrix[unique_count][4] = strdup(base_url); results_inner_counts[unique_count] = INFOBOX_FIELD_COUNT; free(pretty_url); + free(base_url); free(all_results[i][j].url); free(all_results[i][j].title); free(all_results[i][j].snippet); diff --git a/static/main.css b/static/main.css index 31b9d7b..8743248 100644 --- a/static/main.css +++ b/static/main.css @@ -272,6 +272,18 @@ h1 span { gap:60px; padding:30px 60px; } +.result-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 2px; +} +.result-favicon { + width: 16px; + height: 16px; + flex-shrink: 0; + object-fit: contain; +} .results-container { grid-column:2; } diff --git a/templates/results.html b/templates/results.html index 042fbb7..c3ecdc2 100644 --- a/templates/results.html +++ b/templates/results.html @@ -55,9 +55,14 @@ {{for result in results}}
- - {{result[1]}} - +
+
+
+ + {{result[1]}} + +
{{result[2]}} From 660a4918b8fbd6f5131ba2b66c892698f7233956 Mon Sep 17 00:00:00 2001 From: frosty Date: Mon, 23 Mar 2026 03:09:00 -0400 Subject: [PATCH 02/61] style: changed how favicons appear on the result page --- static/main.css | 42 +++++++++++++++++++++++++++++++++++------- templates/results.html | 2 +- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/static/main.css b/static/main.css index 8743248..fa4eeea 100644 --- a/static/main.css +++ b/static/main.css @@ -277,12 +277,38 @@ h1 span { align-items: center; gap: 8px; margin-bottom: 2px; + position: relative; } .result-favicon { width: 16px; height: 16px; flex-shrink: 0; - object-fit: contain; + background-size: cover; + background-position: center; + position: absolute; + left: -24px; +} +.url { + color: var(--text-secondary); + font-size: 0.85rem; + display: block; + margin-bottom: 4px; +} + +@media (max-width: 768px) { + .result-favicon { + width: 14px; + height: 14px; + left: -20px; + } +} + +@media (max-width: 480px) { + .result-favicon { + width: 12px; + height: 12px; + left: -16px; + } } .results-container { grid-column:2; @@ -320,12 +346,6 @@ h1 span { display:inline-block; margin-bottom:4px; } -.url { - color:var(--text-secondary); - font-size:0.85rem; - display:block; - margin-bottom:4px; -} .desc { color:var(--text-muted); line-height:1.6; @@ -438,6 +458,10 @@ h1 span { @media (max-width:1200px) { + body { + padding-left: 16px; + padding-right: 16px; + } .content-layout { grid-template-columns:1fr; padding:20px 30px; @@ -459,6 +483,10 @@ h1 span { } @media (max-width:768px) { + body { + padding-left: 16px; + padding-right: 16px; + } header { flex-direction:column; gap:12px; diff --git a/templates/results.html b/templates/results.html index c3ecdc2..d8e2601 100644 --- a/templates/results.html +++ b/templates/results.html @@ -57,7 +57,7 @@
+ style="background-image: url('https://{{result[4]}}/favicon.ico'), url('https://{{result[4]}}/favicon.png');">
{{result[1]}} From 4ed9ec9fc5155508102da67829cf7fec9e92a50a Mon Sep 17 00:00:00 2001 From: Else Date: Mon, 23 Mar 2026 11:18:20 +0100 Subject: [PATCH 03/61] Add engine filters and result source labels --- src/Routes/Search.c | 332 +++++++++++++++++++++++++++++++++++++---- static/main.css | 44 ++++-- templates/results.html | 61 +++----- 3 files changed, 356 insertions(+), 81 deletions(-) diff --git a/src/Routes/Search.c b/src/Routes/Search.c index b75f398..baf0fdf 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -27,6 +27,12 @@ typedef struct { char *(*url_construct_fn)(const char *query); } InfoBoxHandler; +enum { + RESULT_FIELD_COUNT = 5, + LINK_FIELD_COUNT = 3, + PAGER_WINDOW_SIZE = 5, +}; + static InfoBox fetch_wiki_wrapper(char *query) { char *url = construct_wiki_url(query); if (!url) @@ -180,6 +186,55 @@ static int add_infobox_to_collection(InfoBox *infobox, char ****collection, return current_count + 1; } +static int add_link_to_collection(const char *href, const char *label, + const char *class_name, char ****collection, + int **inner_counts, int current_count) { + char ***new_collection = + (char ***)malloc(sizeof(char **) * (current_count + 1)); + int *new_inner_counts = + (int *)malloc(sizeof(int) * (current_count + 1)); + + if (!new_collection || !new_inner_counts) { + free(new_collection); + free(new_inner_counts); + return current_count; + } + + if (*collection && current_count > 0) { + memcpy(new_collection, *collection, sizeof(char **) * current_count); + } + if (*inner_counts && current_count > 0) { + memcpy(new_inner_counts, *inner_counts, sizeof(int) * current_count); + } + + free(*collection); + free(*inner_counts); + + *collection = new_collection; + *inner_counts = new_inner_counts; + + (*collection)[current_count] = + (char **)malloc(sizeof(char *) * LINK_FIELD_COUNT); + if (!(*collection)[current_count]) + return current_count; + + (*collection)[current_count][0] = strdup(href ? href : ""); + (*collection)[current_count][1] = strdup(label ? label : ""); + (*collection)[current_count][2] = strdup(class_name ? class_name : ""); + + if (!(*collection)[current_count][0] || !(*collection)[current_count][1] || + !(*collection)[current_count][2]) { + free((*collection)[current_count][0]); + free((*collection)[current_count][1]); + free((*collection)[current_count][2]); + free((*collection)[current_count]); + return current_count; + } + + (*inner_counts)[current_count] = LINK_FIELD_COUNT; + return current_count + 1; +} + static int add_warning_to_collection(const char *engine_name, const char *warning_message, char ****collection, int **inner_counts, @@ -241,9 +296,113 @@ static const char *warning_message_for_job(const ScrapeJob *job) { } } +static int engine_id_matches(const char *left, const char *right) { + if (!left || !right) + return 0; + + while (*left && *right) { + char l = *left; + char r = *right; + + if (l >= 'A' && l <= 'Z') + l = l - 'A' + 'a'; + if (r >= 'A' && r <= 'Z') + r = r - 'A' + 'a'; + + if (l != r) + return 0; + + left++; + right++; + } + + return *left == *right; +} + +static const SearchEngine *find_enabled_engine(const char *engine_id) { + if (!engine_id || engine_id[0] == '\0' || engine_id_matches(engine_id, "all")) + return NULL; + + for (int i = 0; i < ENGINE_COUNT; i++) { + if (ENGINE_REGISTRY[i].enabled && + engine_id_matches(ENGINE_REGISTRY[i].id, engine_id)) { + return &ENGINE_REGISTRY[i]; + } + } + + return NULL; +} + +static char *build_search_href(const char *query, const char *engine_id, + int page) { + const char *safe_query = query ? query : ""; + int use_engine = engine_id && engine_id[0] != '\0' && + !engine_id_matches(engine_id, "all"); + size_t needed = strlen("/search?q=") + strlen(safe_query) + 1; + + if (use_engine) + needed += strlen("&engine=") + strlen(engine_id); + if (page > 1) + needed += strlen("&p=") + 16; + + char *href = (char *)malloc(needed); + if (!href) + return NULL; + + snprintf(href, needed, "/search?q=%s", safe_query); + + if (use_engine) { + strcat(href, "&engine="); + strcat(href, engine_id); + } + + if (page > 1) { + char page_buf[16]; + snprintf(page_buf, sizeof(page_buf), "%d", page); + strcat(href, "&p="); + strcat(href, page_buf); + } + + return href; +} + +static char *build_result_sources(unsigned int source_mask, ScrapeJob *jobs, + int job_count) { + size_t needed = 1; + int source_count = 0; + + for (int i = 0; i < job_count; i++) { + if (source_mask & (1u << i)) { + needed += strlen(jobs[i].engine->name); + if (source_count > 0) + needed += strlen(" · "); + source_count++; + } + } + + char *sources = (char *)malloc(needed); + if (!sources) + return NULL; + + sources[0] = '\0'; + source_count = 0; + + for (int i = 0; i < job_count; i++) { + if (source_mask & (1u << i)) { + if (source_count > 0) + strcat(sources, " · "); + strcat(sources, jobs[i].engine->name); + source_count++; + } + } + + return sources; +} + int results_handler(UrlParams *params) { TemplateContext ctx = new_context(); char *raw_query = ""; + const char *selected_engine_id = "all"; int page = 1; int btnI = 0; @@ -255,6 +414,8 @@ int results_handler(UrlParams *params) { int parsed = atoi(params->params[i].value); if (parsed > 1) page = parsed; + } else if (strcmp(params->params[i].key, "engine") == 0) { + selected_engine_id = params->params[i].value; } else if (strcmp(params->params[i].key, "btnI") == 0) { btnI = atoi(params->params[i].value); } @@ -262,19 +423,9 @@ int results_handler(UrlParams *params) { } context_set(&ctx, "query", raw_query); - - char page_str[16], prev_str[16], next_str[16], two_prev_str[16], - two_next_str[16]; + char page_str[16]; snprintf(page_str, sizeof(page_str), "%d", page); - snprintf(prev_str, sizeof(prev_str), "%d", page > 1 ? page - 1 : 0); - snprintf(next_str, sizeof(next_str), "%d", page + 1); - snprintf(two_prev_str, sizeof(two_prev_str), "%d", page > 2 ? page - 2 : 0); - snprintf(two_next_str, sizeof(two_next_str), "%d", page + 2); context_set(&ctx, "page", page_str); - context_set(&ctx, "prev_page", prev_str); - context_set(&ctx, "next_page", next_str); - context_set(&ctx, "two_prev_page", two_prev_str); - context_set(&ctx, "two_next_page", two_next_str); if (!raw_query || strlen(raw_query) == 0) { send_response("

No query provided

"); @@ -282,6 +433,23 @@ int results_handler(UrlParams *params) { return -1; } + const SearchEngine *selected_engine = find_enabled_engine(selected_engine_id); + if (!selected_engine) + selected_engine_id = "all"; + + context_set(&ctx, "selected_engine", selected_engine_id); + char *search_href = build_search_href(raw_query, selected_engine_id, 1); + context_set(&ctx, "search_href", search_href ? search_href : "/search"); + free(search_href); + + int enabled_engine_count = 0; + for (int i = 0; i < ENGINE_COUNT; i++) { + if (ENGINE_REGISTRY[i].enabled && + (!selected_engine || &ENGINE_REGISTRY[i] == selected_engine)) { + enabled_engine_count++; + } + } + pthread_t infobox_threads[HANDLER_COUNT]; InfoBoxThreadData infobox_data[HANDLER_COUNT]; @@ -298,19 +466,13 @@ int results_handler(UrlParams *params) { } } - int enabled_engine_count = 0; - for (int i = 0; i < ENGINE_COUNT; i++) { - if (ENGINE_REGISTRY[i].enabled) { - enabled_engine_count++; - } - } - ScrapeJob jobs[ENGINE_COUNT]; SearchResult *all_results[ENGINE_COUNT]; int engine_idx = 0; for (int i = 0; i < ENGINE_COUNT; i++) { - if (ENGINE_REGISTRY[i].enabled) { + if (ENGINE_REGISTRY[i].enabled && + (!selected_engine || &ENGINE_REGISTRY[i] == selected_engine)) { all_results[engine_idx] = NULL; jobs[engine_idx].engine = &ENGINE_REGISTRY[i]; jobs[engine_idx].query = raw_query; @@ -328,8 +490,56 @@ int results_handler(UrlParams *params) { } } - if (enabled_engine_count > 0) { - scrape_engines_parallel(jobs, enabled_engine_count); + int filter_engine_count = 0; + for (int i = 0; i < ENGINE_COUNT; i++) { + if (ENGINE_REGISTRY[i].enabled) + filter_engine_count++; + } + + if (filter_engine_count > 1) { + char ***filter_matrix = NULL; + int *filter_inner_counts = NULL; + int filter_count = 0; + char *all_href = build_search_href(raw_query, "all", 1); + + filter_count = add_link_to_collection( + all_href, "All", + selected_engine ? "engine-filter" : "engine-filter active", + &filter_matrix, &filter_inner_counts, filter_count); + free(all_href); + + for (int i = 0; i < ENGINE_COUNT; i++) { + if (!ENGINE_REGISTRY[i].enabled) + continue; + + char *filter_href = + build_search_href(raw_query, ENGINE_REGISTRY[i].id, 1); + const char *filter_class = + (selected_engine && &ENGINE_REGISTRY[i] == selected_engine) + ? "engine-filter active" + : "engine-filter"; + + filter_count = add_link_to_collection(filter_href, ENGINE_REGISTRY[i].name, + filter_class, &filter_matrix, + &filter_inner_counts, filter_count); + free(filter_href); + } + + if (filter_count > 0) { + context_set_array_of_arrays(&ctx, "engine_filters", filter_matrix, + filter_count, filter_inner_counts); + for (int i = 0; i < filter_count; i++) { + for (int j = 0; j < LINK_FIELD_COUNT; j++) + free(filter_matrix[i][j]); + free(filter_matrix[i]); + } + free(filter_matrix); + free(filter_inner_counts); + } + } + + if (engine_idx > 0) { + scrape_engines_parallel(jobs, engine_idx); } if (page == 1) { @@ -339,7 +549,7 @@ int results_handler(UrlParams *params) { } if (btnI) { - for (int i = 0; i < enabled_engine_count; i++) { + for (int i = 0; i < engine_idx; i++) { if (jobs[i].results_count > 0 && all_results[i][0].url) { char *redirect_url = strdup(all_results[i][0].url); for (int j = 0; j < enabled_engine_count; j++) { @@ -453,13 +663,17 @@ int results_handler(UrlParams *params) { char ***results_matrix = (char ***)malloc(sizeof(char **) * total_results); int *results_inner_counts = (int *)malloc(sizeof(int) * total_results); char **seen_urls = (char **)malloc(sizeof(char *) * total_results); - if (!results_matrix || !results_inner_counts || !seen_urls) { + unsigned int *source_masks = + (unsigned int *)calloc(total_results, sizeof(unsigned int)); + if (!results_matrix || !results_inner_counts || !seen_urls || !source_masks) { if (results_matrix) free(results_matrix); if (results_inner_counts) free(results_inner_counts); if (seen_urls) free(seen_urls); + if (source_masks) + free(source_masks); char *html = render_template("results.html", &ctx); if (html) { send_response(html); @@ -487,6 +701,7 @@ int results_handler(UrlParams *params) { for (int k = 0; k < unique_count; k++) { if (strcmp(seen_urls[k], display_url) == 0) { is_duplicate = 1; + source_masks[k] |= (1u << i); break; } } @@ -506,7 +721,7 @@ int results_handler(UrlParams *params) { continue; } results_matrix[unique_count] = - (char **)malloc(sizeof(char *) * INFOBOX_FIELD_COUNT); + (char **)malloc(sizeof(char *) * RESULT_FIELD_COUNT); if (!results_matrix[unique_count]) { free(seen_urls[unique_count]); free(all_results[i][j].url); @@ -515,7 +730,6 @@ int results_handler(UrlParams *params) { continue; } char *pretty_url = pretty_display_url(display_url); - char *base_url = get_base_url(display_url); results_matrix[unique_count][0] = strdup(display_url); results_matrix[unique_count][1] = strdup(pretty_url); @@ -525,12 +739,12 @@ int results_handler(UrlParams *params) { results_matrix[unique_count][3] = all_results[i][j].snippet ? strdup(all_results[i][j].snippet) : strdup(""); - results_matrix[unique_count][4] = strdup(base_url); + results_matrix[unique_count][4] = NULL; - results_inner_counts[unique_count] = INFOBOX_FIELD_COUNT; + source_masks[unique_count] = (1u << i); + results_inner_counts[unique_count] = RESULT_FIELD_COUNT; free(pretty_url); - free(base_url); free(all_results[i][j].url); free(all_results[i][j].title); free(all_results[i][j].snippet); @@ -540,9 +754,68 @@ int results_handler(UrlParams *params) { free(all_results[i]); } + for (int i = 0; i < unique_count; i++) { + results_matrix[i][4] = + build_result_sources(source_masks[i], jobs, enabled_engine_count); + if (!results_matrix[i][4]) + results_matrix[i][4] = strdup(""); + } + context_set_array_of_arrays(&ctx, "results", results_matrix, unique_count, results_inner_counts); + char ***pager_matrix = NULL; + int *pager_inner_counts = NULL; + int pager_count = 0; + int pager_start = page <= 3 ? 1 : page - 2; + int pager_end = pager_start + PAGER_WINDOW_SIZE - 1; + + if (page > 3) { + char *first_href = build_search_href(raw_query, selected_engine_id, 1); + pager_count = add_link_to_collection(first_href, "First", "pagination-btn", + &pager_matrix, &pager_inner_counts, + pager_count); + free(first_href); + } + + if (page > 1) { + char *prev_href = + build_search_href(raw_query, selected_engine_id, page - 1); + pager_count = add_link_to_collection(prev_href, "Prev", "pagination-btn", + &pager_matrix, &pager_inner_counts, + pager_count); + free(prev_href); + } + + for (int i = pager_start; i <= pager_end; i++) { + char label[16]; + snprintf(label, sizeof(label), "%d", i); + char *page_href = build_search_href(raw_query, selected_engine_id, i); + pager_count = add_link_to_collection( + page_href, label, + i == page ? "pagination-btn pagination-current" : "pagination-btn", + &pager_matrix, &pager_inner_counts, pager_count); + free(page_href); + } + + char *next_href = build_search_href(raw_query, selected_engine_id, page + 1); + pager_count = add_link_to_collection(next_href, "Next", "pagination-btn", + &pager_matrix, &pager_inner_counts, + pager_count); + free(next_href); + + if (pager_count > 0) { + context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix, + pager_count, pager_inner_counts); + for (int i = 0; i < pager_count; i++) { + for (int j = 0; j < LINK_FIELD_COUNT; j++) + free(pager_matrix[i][j]); + free(pager_matrix[i]); + } + free(pager_matrix); + free(pager_inner_counts); + } + char *html = render_template("results.html", &ctx); if (html) { send_response(html); @@ -550,12 +823,13 @@ int results_handler(UrlParams *params) { } for (int i = 0; i < unique_count; i++) { - for (int j = 0; j < INFOBOX_FIELD_COUNT; j++) + for (int j = 0; j < RESULT_FIELD_COUNT; j++) free(results_matrix[i][j]); free(results_matrix[i]); free(seen_urls[i]); } free(seen_urls); + free(source_masks); free(results_matrix); free(results_inner_counts); } else { diff --git a/static/main.css b/static/main.css index fa4eeea..e415e66 100644 --- a/static/main.css +++ b/static/main.css @@ -294,6 +294,12 @@ h1 span { display: block; margin-bottom: 4px; } +.result-sources { + color:var(--text-secondary); + display:block; + font-size:0.78rem; + margin-bottom:8px; +} @media (max-width: 768px) { .result-favicon { @@ -313,6 +319,27 @@ h1 span { .results-container { grid-column:2; } +.engine-filter-list { + display:flex; + flex-wrap:wrap; + gap:10px; + margin-bottom:24px; +} +.engine-filter { + background:var(--bg-card); + color:var(--text-secondary); + border:1px solid var(--border); + border-radius:999px; + padding:6px 12px; + text-decoration:none; + font-size:0.85rem; + font-weight:600; +} +.engine-filter.active { + background:var(--accent); + border-color:var(--accent); + color:var(--bg-main); +} .engine-warning-list { display:flex; flex-direction:column; @@ -439,21 +466,14 @@ h1 span { .pagination-current { - background: var(--bg-card); - color: var(--text-primary); - border: 1px solid var(--border); - padding: 4px 12px; - border-radius: 8px; - text-decoration: none; - font-size: 1.2rem; - font-weight: 600; - transition: all 0.2s; - touch-action: manipulation; + background: var(--accent); + border-color: var(--accent); + color: var(--bg-main); } .pagination-current:hover { - background: var(--border); - border-color: var(--text-secondary); + background: var(--accent); + border-color: var(--accent); } diff --git a/templates/results.html b/templates/results.html index d8e2601..f0b0ca0 100644 --- a/templates/results.html +++ b/templates/results.html @@ -20,13 +20,14 @@ OmniSearch
+
{{endfor}} + {{if exists pagination_links}} + {{endif}}
From e9b01902d954a54e94e2f77be1bfa1de7cb410e1 Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 24 Mar 2026 15:37:13 -0400 Subject: [PATCH 06/61] removed search engine indicator from results --- src/Routes/Search.c | 51 ++---------------------------------------- static/main.css | 7 +----- templates/results.html | 2 -- 3 files changed, 3 insertions(+), 57 deletions(-) diff --git a/src/Routes/Search.c b/src/Routes/Search.c index 1d0a205..5f89752 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -377,39 +377,6 @@ static char *build_search_href(const char *query, const char *engine_id, return href; } -static char *build_result_sources(unsigned int source_mask, ScrapeJob *jobs, - int job_count) { - size_t needed = 1; - int source_count = 0; - - for (int i = 0; i < job_count; i++) { - if (source_mask & (1u << i)) { - needed += strlen(jobs[i].engine->name); - if (source_count > 0) - needed += strlen(" · "); - source_count++; - } - } - - char *sources = (char *)malloc(needed); - if (!sources) - return NULL; - - sources[0] = '\0'; - source_count = 0; - - for (int i = 0; i < job_count; i++) { - if (source_mask & (1u << i)) { - if (source_count > 0) - strcat(sources, " · "); - strcat(sources, jobs[i].engine->name); - source_count++; - } - } - - return sources; -} - int results_handler(UrlParams *params) { TemplateContext ctx = new_context(); char *raw_query = ""; @@ -674,17 +641,13 @@ int results_handler(UrlParams *params) { char ***results_matrix = (char ***)malloc(sizeof(char **) * total_results); int *results_inner_counts = (int *)malloc(sizeof(int) * total_results); char **seen_urls = (char **)malloc(sizeof(char *) * total_results); - unsigned int *source_masks = - (unsigned int *)calloc(total_results, sizeof(unsigned int)); - if (!results_matrix || !results_inner_counts || !seen_urls || !source_masks) { + if (!results_matrix || !results_inner_counts || !seen_urls) { if (results_matrix) free(results_matrix); if (results_inner_counts) free(results_inner_counts); if (seen_urls) free(seen_urls); - if (source_masks) - free(source_masks); char *html = render_template("results.html", &ctx); if (html) { send_response(html); @@ -712,7 +675,6 @@ int results_handler(UrlParams *params) { for (int k = 0; k < unique_count; k++) { if (strcmp(seen_urls[k], display_url) == 0) { is_duplicate = 1; - source_masks[k] |= (1u << i); break; } } @@ -752,9 +714,8 @@ int results_handler(UrlParams *params) { all_results[i][j].snippet ? strdup(all_results[i][j].snippet) : strdup(""); results_matrix[unique_count][4] = strdup(base_url ? base_url : ""); - results_matrix[unique_count][5] = NULL; + results_matrix[unique_count][5] = strdup(""); - source_masks[unique_count] = (1u << i); results_inner_counts[unique_count] = RESULT_FIELD_COUNT; free(pretty_url); @@ -768,13 +729,6 @@ int results_handler(UrlParams *params) { free(all_results[i]); } - for (int i = 0; i < unique_count; i++) { - results_matrix[i][5] = - build_result_sources(source_masks[i], jobs, enabled_engine_count); - if (!results_matrix[i][5]) - results_matrix[i][5] = strdup(""); - } - context_set_array_of_arrays(&ctx, "results", results_matrix, unique_count, results_inner_counts); @@ -843,7 +797,6 @@ int results_handler(UrlParams *params) { free(seen_urls[i]); } free(seen_urls); - free(source_masks); free(results_matrix); free(results_inner_counts); } else { diff --git a/static/main.css b/static/main.css index e415e66..6f899cf 100644 --- a/static/main.css +++ b/static/main.css @@ -294,12 +294,7 @@ h1 span { display: block; margin-bottom: 4px; } -.result-sources { - color:var(--text-secondary); - display:block; - font-size:0.78rem; - margin-bottom:8px; -} + @media (max-width: 768px) { .result-favicon { diff --git a/templates/results.html b/templates/results.html index ed8b04f..5207057 100644 --- a/templates/results.html +++ b/templates/results.html @@ -73,8 +73,6 @@ {{result[1]}} - - {{result[5]}}
From 783a58d95487a1a8e97f1cc5f2fc58016e695b7e Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 24 Mar 2026 16:03:31 -0400 Subject: [PATCH 07/61] feat: ignore query parameters in formatted URLs for readability --- src/Utility/Display.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Utility/Display.c b/src/Utility/Display.c index 1322391..aee6929 100644 --- a/src/Utility/Display.c +++ b/src/Utility/Display.c @@ -25,6 +25,12 @@ char *pretty_display_url(const char *input) { strncpy(temp, start, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; + char *query = strchr(temp, '?'); + if (query) { + *query = '\0'; + input_len = strlen(temp); + } + if (input_len > 0 && temp[input_len - 1] == '/') { temp[input_len - 1] = '\0'; } From 86a9ebb90a2ac07d836c1408e3a15feb8615bd62 Mon Sep 17 00:00:00 2001 From: DinoShrimp <69805161+adinoshrimp@users.noreply.github.com> Date: Fri, 20 Mar 2026 22:57:00 +0100 Subject: [PATCH 08/61] Add docker compose --- Dockerfile | 39 +++++++++++++++++++++++++++++++++++++++ README.md | 14 ++++++++++++++ docker-compose.yml | 7 +++++++ 3 files changed, 60 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..14b67b8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM alpine:latest + +# Install required dependencies +RUN apk add --no-cache \ + libxml2-dev \ + curl-dev \ + shadow \ + git \ + make \ + gcc \ + musl-dev \ + pkgconf \ + openssl-dev \ + openrc + +# Clone and install beaker +RUN git clone https://git.bwaaa.monster/beaker /tmp/beaker \ + && cd /tmp/beaker \ + && make \ + && make install \ + && rm -rf /tmp/beaker + +# Import omnisearch source +WORKDIR /app +COPY . /app + +# Clone and install omnisearch +RUN cd /app \ + && make \ + && make install-openrc + +# Enable OpenRC and start the service +RUN rc-update add omnisearch default + +# Expose the default port +EXPOSE 5000 + +# Start OpenRC and the service +CMD sh -c "openrc default && touch /run/openrc/softlevel && omnisearch" diff --git a/README.md b/README.md index 6e7a176..50a8d3c 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,20 @@ On macOS, use `install-launchd`. ## Hosting Run it normally behind a reverse proxy (like nginx) +## Deploy with Docker Compose + +You need Docker or Podman and Docker Compose installed on your system. + +Run the container: + +``` +$ git clone https://git.bwaaa.monster/omnisearch +$ cd omnisearch +$ docker compose up -d --build +``` + +By default it can be reached on port 5000. + ## Customisation To make your own changes while still being able to receive upstream updates: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..716b3f9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + omnisearch: + build: + context: . + dockerfile: Dockerfile + ports: + - "5000:5000" From 1b9187b1534992c0235dad064238b2d836489ca8 Mon Sep 17 00:00:00 2001 From: frosty Date: Sat, 28 Mar 2026 15:01:13 -0400 Subject: [PATCH 09/61] feat: changes to image proxy, proxy favicons --- src/Cache/Cache.c | 5 + src/Cache/Cache.h | 2 + src/Config.c | 2 + src/Config.h | 2 + src/Main.c | 2 + src/Routes/ImageProxy.c | 213 +++++++++++++++++++++++++++++++++++++--- templates/results.html | 2 +- 7 files changed, 212 insertions(+), 16 deletions(-) diff --git a/src/Cache/Cache.c b/src/Cache/Cache.c index d739277..22f2553 100644 --- a/src/Cache/Cache.c +++ b/src/Cache/Cache.c @@ -11,15 +11,20 @@ static char cache_dir[BUFFER_SIZE_MEDIUM] = {0}; static int cache_ttl_search_val = DEFAULT_CACHE_TTL_SEARCH; static int cache_ttl_infobox_val = DEFAULT_CACHE_TTL_INFOBOX; +static int cache_ttl_image_val = DEFAULT_CACHE_TTL_IMAGE; void set_cache_ttl_search(int ttl) { cache_ttl_search_val = ttl; } void set_cache_ttl_infobox(int ttl) { cache_ttl_infobox_val = ttl; } +void set_cache_ttl_image(int ttl) { cache_ttl_image_val = ttl; } + int get_cache_ttl_search(void) { return cache_ttl_search_val; } int get_cache_ttl_infobox(void) { return cache_ttl_infobox_val; } +int get_cache_ttl_image(void) { return cache_ttl_image_val; } + static void md5_hash(const char *str, char *output) { unsigned char hash[EVP_MAX_MD_SIZE]; unsigned int hash_len; diff --git a/src/Cache/Cache.h b/src/Cache/Cache.h index 0a84406..9989b43 100644 --- a/src/Cache/Cache.h +++ b/src/Cache/Cache.h @@ -17,7 +17,9 @@ char *cache_compute_key(const char *query, int page, const char *engine_name); void set_cache_ttl_search(int ttl); void set_cache_ttl_infobox(int ttl); +void set_cache_ttl_image(int ttl); int get_cache_ttl_search(void); int get_cache_ttl_infobox(void); +int get_cache_ttl_image(void); #endif diff --git a/src/Config.c b/src/Config.c index 0c243bd..0c3fc1c 100644 --- a/src/Config.c +++ b/src/Config.c @@ -92,6 +92,8 @@ int load_config(const char *filename, Config *config) { config->cache_ttl_search = atoi(value); } else if (strcmp(key, "ttl_infobox") == 0) { config->cache_ttl_infobox = atoi(value); + } else if (strcmp(key, "ttl_image") == 0) { + config->cache_ttl_image = atoi(value); } } else if (strcmp(section, "engines") == 0) { if (strcmp(key, "engines") == 0) { diff --git a/src/Config.h b/src/Config.h index 67882d1..ce316f6 100644 --- a/src/Config.h +++ b/src/Config.h @@ -6,6 +6,7 @@ #define DEFAULT_CACHE_DIR "/tmp/omnisearch_cache" #define DEFAULT_CACHE_TTL_SEARCH 3600 #define DEFAULT_CACHE_TTL_INFOBOX 86400 +#define DEFAULT_CACHE_TTL_IMAGE 604800 #define DEFAULT_MAX_PROXY_RETRIES 3 #define BUFFER_SIZE_SMALL 256 @@ -42,6 +43,7 @@ typedef struct { char cache_dir[512]; int cache_ttl_search; int cache_ttl_infobox; + int cache_ttl_image; char engines[512]; } Config; diff --git a/src/Main.c b/src/Main.c index 8aa161d..137cab5 100644 --- a/src/Main.c +++ b/src/Main.c @@ -52,6 +52,7 @@ int main() { .cache_dir = DEFAULT_CACHE_DIR, .cache_ttl_search = DEFAULT_CACHE_TTL_SEARCH, .cache_ttl_infobox = DEFAULT_CACHE_TTL_INFOBOX, + .cache_ttl_image = DEFAULT_CACHE_TTL_IMAGE, .engines = ""}; if (load_config("config.ini", &cfg) != 0) { @@ -72,6 +73,7 @@ int main() { set_cache_ttl_search(cfg.cache_ttl_search); set_cache_ttl_infobox(cfg.cache_ttl_infobox); + set_cache_ttl_image(cfg.cache_ttl_image); if (cfg.proxy_list_file[0] != '\0') { if (load_proxy_list(cfg.proxy_list_file) < 0) { diff --git a/src/Routes/ImageProxy.c b/src/Routes/ImageProxy.c index c2d1a9a..670da68 100644 --- a/src/Routes/ImageProxy.c +++ b/src/Routes/ImageProxy.c @@ -1,9 +1,16 @@ #include "ImageProxy.h" +#include "../Cache/Cache.h" #include "../Proxy/Proxy.h" +#include #include +#include +#include +#include +#include #include #include #include +#include #define MAX_IMAGE_SIZE (10 * 1024 * 1024) @@ -13,7 +20,97 @@ typedef struct { size_t capacity; } MemoryBuffer; +static int is_private_ip(const char *ip_str) { + struct in_addr addr; + if (inet_pton(AF_INET, ip_str, &addr) != 1) { + return 0; + } + + uint32_t ip = ntohl(addr.s_addr); + + // 10.0.0.0/8 + if ((ip >> 24) == 10) { + return 1; + } + + // 172.16.0.0/12 + if ((ip >> 20) == 0xAC) { + uint8_t second = (ip >> 16) & 0xFF; + if (second >= 16 && second <= 31) { + return 1; + } + } + + // 192.168.0.0/16 + if ((ip >> 16) == 0xC0A8) { + return 1; + } + + // 127.0.0.0/8 + if ((ip >> 24) == 127) { + return 1; + } + + // 169.254.0.0/16 + if ((ip >> 16) == 0xA9FE) { + return 1; + } + + return 0; +} + +static int is_private_hostname(const char *hostname) { + struct addrinfo hints, *res, *p; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + int err = getaddrinfo(hostname, NULL, &hints, &res); + if (err != 0) { + return 0; + } + + for (p = res; p != NULL; p = p->ai_next) { + if (p->ai_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr; + char ip_str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(ipv4->sin_addr), ip_str, INET_ADDRSTRLEN); + + if (is_private_ip(ip_str)) { + freeaddrinfo(res); + return 1; + } + } + } + + freeaddrinfo(res); + return 0; +} + static int is_allowed_domain(const char *url) { + CURLU *h = curl_url(); + if (!h) { + return -1; + } + + curl_url_set(h, CURLUPART_URL, url, 0); + + char *scheme = NULL; + curl_url_get(h, CURLUPART_SCHEME, &scheme, 0); + + int valid_scheme = 0; + if (scheme && (strcasecmp(scheme, "http") == 0 || strcasecmp(scheme, "https") == 0)) { + valid_scheme = 1; + } + + if (scheme) + curl_free(scheme); + + if (!valid_scheme) { + curl_url_cleanup(h); + return -1; + } + const char *protocol = strstr(url, "://"); if (!protocol) { protocol = url; @@ -30,21 +127,18 @@ static int is_allowed_domain(const char *url) { } strncpy(host, protocol, host_len); - const char *allowed_domains[] = {"mm.bing.net", "th.bing.com", NULL}; - - for (int i = 0; allowed_domains[i] != NULL; i++) { - size_t domain_len = strlen(allowed_domains[i]); - size_t host_str_len = strlen(host); - - if (host_str_len >= domain_len) { - const char *suffix = host + host_str_len - domain_len; - if (strcmp(suffix, allowed_domains[i]) == 0) { - return 1; - } - } + char *colon = strchr(host, ':'); + if (colon) { + *colon = '\0'; } - return 0; + if (is_private_hostname(host)) { + curl_url_cleanup(h); + return 0; + } + + curl_url_cleanup(h); + return 1; } static size_t write_callback(void *contents, size_t size, size_t nmemb, @@ -73,6 +167,31 @@ static size_t write_callback(void *contents, size_t size, size_t nmemb, return realsize; } +static char *url_encode_key(const char *url) { + char *hash = malloc(33); + if (!hash) + return NULL; + + unsigned char md5hash[16]; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) { + free(hash); + return NULL; + } + + EVP_DigestInit_ex(ctx, EVP_md5(), NULL); + EVP_DigestUpdate(ctx, url, strlen(url)); + EVP_DigestFinal_ex(ctx, md5hash, NULL); + EVP_MD_CTX_free(ctx); + + for (int i = 0; i < 16; i++) { + sprintf(hash + (i * 2), "%02x", md5hash[i]); + } + hash[32] = '\0'; + + return hash; +} + int image_proxy_handler(UrlParams *params) { const char *url = NULL; for (int i = 0; i < params->count; i++) { @@ -87,13 +206,59 @@ int image_proxy_handler(UrlParams *params) { return 0; } - if (!is_allowed_domain(url)) { - send_response("Domain not allowed"); + int domain_check = is_allowed_domain(url); + if (domain_check == -1) { + send_response("Invalid URL scheme"); + return 0; + } + if (domain_check == 0) { + send_response("Private addresses are not allowed"); + return 0; + } + + char *cache_key = url_encode_key(url); + if (!cache_key) { + send_response("Failed to generate cache key"); + return 0; + } + + char *cached_data = NULL; + size_t cached_size = 0; + int cache_ttl = get_cache_ttl_image(); + + if (cache_get(cache_key, cache_ttl, &cached_data, &cached_size) == 0) { + char content_type[64] = {0}; + + const char *ext = strrchr(url, '.'); + if (ext) { + if (strcasecmp(ext, ".png") == 0) { + strncpy(content_type, "image/png", sizeof(content_type) - 1); + } else if (strcasecmp(ext, ".gif") == 0) { + strncpy(content_type, "image/gif", sizeof(content_type) - 1); + } else if (strcasecmp(ext, ".webp") == 0) { + strncpy(content_type, "image/webp", sizeof(content_type) - 1); + } else if (strcasecmp(ext, ".svg") == 0) { + strncpy(content_type, "image/svg+xml", sizeof(content_type) - 1); + } else if (strcasecmp(ext, ".ico") == 0) { + strncpy(content_type, "image/x-icon", sizeof(content_type) - 1); + } else if (strcasecmp(ext, ".bmp") == 0) { + strncpy(content_type, "image/bmp", sizeof(content_type) - 1); + } + } + + if (strlen(content_type) == 0) { + strncpy(content_type, "image/jpeg", sizeof(content_type) - 1); + } + + serve_data(cached_data, cached_size, content_type); + free(cached_data); + free(cache_key); return 0; } CURL *curl = curl_easy_init(); if (!curl) { + free(cache_key); send_response("Failed to initialize curl"); return 0; } @@ -102,6 +267,7 @@ int image_proxy_handler(UrlParams *params) { if (!buf.data) { curl_easy_cleanup(curl); + free(cache_key); send_response("Memory allocation failed"); return 0; } @@ -111,6 +277,10 @@ int image_proxy_handler(UrlParams *params) { curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); + curl_easy_setopt(curl, CURLOPT_USERAGENT, + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/120.0.0.0 Safari/537.36"); apply_proxy_settings(curl); CURLcode res = curl_easy_perform(curl); @@ -130,14 +300,27 @@ int image_proxy_handler(UrlParams *params) { if (res != CURLE_OK || response_code != 200) { free(buf.data); + free(cache_key); send_response("Failed to fetch image"); return 0; } + if (strlen(content_type) == 0 || + strncmp(content_type, "image/", 6) != 0) { + free(buf.data); + free(cache_key); + send_response("Invalid content type"); + return 0; + } + const char *mime_type = strlen(content_type) > 0 ? content_type : "image/jpeg"; + + cache_set(cache_key, buf.data, buf.size); + serve_data(buf.data, buf.size, mime_type); free(buf.data); + free(cache_key); return 0; } diff --git a/templates/results.html b/templates/results.html index 5207057..57c2265 100644 --- a/templates/results.html +++ b/templates/results.html @@ -68,7 +68,7 @@
+ style="background-image: url('/proxy?url=https://{{result[4]}}/favicon.ico'), url('/proxy?url=https://{{result[4]}}/favicon.png');">
{{result[1]}} From 82075a664e181c0a6a064ad7767d43d294db580a Mon Sep 17 00:00:00 2001 From: frosty Date: Sat, 28 Mar 2026 15:20:56 -0400 Subject: [PATCH 10/61] fix: pagination on images page looked improper --- templates/images.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/images.html b/templates/images.html index b20eb04..f04867b 100644 --- a/templates/images.html +++ b/templates/images.html @@ -76,7 +76,7 @@
{{endif}} - + {{page}}
diff --git a/templates/images.html b/templates/images.html index f04867b..92ab7b5 100644 --- a/templates/images.html +++ b/templates/images.html @@ -7,8 +7,10 @@ OmniSearch Images - {{query}} - + + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} @@ -20,6 +22,7 @@ +
diff --git a/templates/results.html b/templates/results.html index 57c2265..4128245 100644 --- a/templates/results.html +++ b/templates/results.html @@ -8,7 +8,9 @@ OmniSearch - {{query}} - + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} + @@ -24,6 +26,7 @@ +
diff --git a/templates/settings.html b/templates/settings.html new file mode 100644 index 0000000..780e438 --- /dev/null +++ b/templates/settings.html @@ -0,0 +1,67 @@ + + + + + + + + OmniSearch - Settings + + + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} + + + + + +
+

+ OmniSearch +

+
+ +
+ +
+ +
+
+
+ +
+

Theme

+

Choose your preferred colour scheme.

+
+ + +
+
+
+ +
+
+
+
+ + + From f38fe3c42ec01efe37820b0c00dd79a66c80c0de Mon Sep 17 00:00:00 2001 From: stab Date: Tue, 31 Mar 2026 04:57:15 +0300 Subject: [PATCH 16/61] Added rate limiting and settings fixes. --- example-config.ini | 11 +++ src/Config.c | 10 +++ src/Config.h | 4 + src/Limiter/RateLimit.c | 193 ++++++++++++++++++++++++++++++++++++++++ src/Limiter/RateLimit.h | 20 +++++ src/Main.c | 6 +- src/Routes/Images.c | 56 ++++++++++++ src/Routes/Search.c | 58 ++++++++++++ templates/settings.html | 6 ++ 9 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 src/Limiter/RateLimit.c create mode 100644 src/Limiter/RateLimit.h diff --git a/example-config.ini b/example-config.ini index fc6ea8d..5145d88 100644 --- a/example-config.ini +++ b/example-config.ini @@ -31,3 +31,14 @@ domain = https://search.example.com # Use *,-engine to exclude specific engines (e.g., *,-startpage) # Available engines: ddg, startpage, yahoo, mojeek engines="*" + +[rate_limit] +# Rate limit searches per interval + +# /search +#search_requests = 10 +#search_interval = 60 + +# /images +#images_requests = 20 +#images_interval = 60 diff --git a/src/Config.c b/src/Config.c index 0c3fc1c..c4bd1f1 100644 --- a/src/Config.c +++ b/src/Config.c @@ -100,6 +100,16 @@ int load_config(const char *filename, Config *config) { strncpy(config->engines, value, sizeof(config->engines) - 1); config->engines[sizeof(config->engines) - 1] = '\0'; } + } else if (strcmp(section, "rate_limit") == 0) { + if (strcmp(key, "search_requests") == 0) { + config->rate_limit_search_requests = atoi(value); + } else if (strcmp(key, "search_interval") == 0) { + config->rate_limit_search_interval = atoi(value); + } else if (strcmp(key, "images_requests") == 0) { + config->rate_limit_images_requests = atoi(value); + } else if (strcmp(key, "images_interval") == 0) { + config->rate_limit_images_interval = atoi(value); + } } } } diff --git a/src/Config.h b/src/Config.h index ce316f6..8e68eae 100644 --- a/src/Config.h +++ b/src/Config.h @@ -45,6 +45,10 @@ typedef struct { int cache_ttl_infobox; int cache_ttl_image; char engines[512]; + int rate_limit_search_requests; + int rate_limit_search_interval; + int rate_limit_images_requests; + int rate_limit_images_interval; } Config; int load_config(const char *filename, Config *config); diff --git a/src/Limiter/RateLimit.c b/src/Limiter/RateLimit.c new file mode 100644 index 0000000..3c6bbff --- /dev/null +++ b/src/Limiter/RateLimit.c @@ -0,0 +1,193 @@ +#include "RateLimit.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct RateLimitEntry { + char client_key[64]; + char scope[32]; + time_t window_start; + time_t last_seen; + int count; + struct RateLimitEntry *next; +} RateLimitEntry; + +extern __thread int current_client_socket; +extern __thread char current_request_buffer[]; + +static pthread_mutex_t rate_limit_mutex = PTHREAD_MUTEX_INITIALIZER; +static RateLimitEntry *rate_limit_entries = NULL; + +static int is_blank_char(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static void trim_copy(char *dest, size_t dest_size, const char *src, + size_t src_len) { + while (src_len > 0 && is_blank_char(*src)) { + src++; + src_len--; + } + + while (src_len > 0 && is_blank_char(src[src_len - 1])) { + src_len--; + } + + if (dest_size == 0) + return; + + if (src_len >= dest_size) + src_len = dest_size - 1; + + memcpy(dest, src, src_len); + dest[src_len] = '\0'; +} + +static void get_client_key(char *client_key, size_t client_key_size) { + const char *header = strstr(current_request_buffer, "X-Forwarded-For:"); + if (!header) + return; + + header += strlen("X-Forwarded-For:"); + const char *line_end = strpbrk(header, "\r\n"); + size_t line_len = line_end ? (size_t)(line_end - header) : strlen(header); + const char *comma = memchr(header, ',', line_len); + size_t value_len = comma ? (size_t)(comma - header) : line_len; + + trim_copy(client_key, client_key_size, header, value_len); +} + +static void get_client_key_from_socket(char *client_key, + size_t client_key_size) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (getpeername(current_client_socket, (struct sockaddr *)&addr, &addr_len) != + 0) { + return; + } + + if (addr.ss_family == AF_INET) { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)&addr; + inet_ntop(AF_INET, &ipv4->sin_addr, client_key, client_key_size); + } else if (addr.ss_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)&addr; + inet_ntop(AF_INET6, &ipv6->sin6_addr, client_key, client_key_size); + } else if (addr.ss_family == AF_UNIX) { + snprintf(client_key, client_key_size, "unix:%d", current_client_socket); + } +} + +void rate_limit_get_client_key(char *client_key, size_t client_key_size) { + if (!client_key || client_key_size == 0) + return; + + client_key[0] = '\0'; + get_client_key(client_key, client_key_size); + + if (client_key[0] == '\0') { + get_client_key_from_socket(client_key, client_key_size); + } + + if (client_key[0] == '\0') { + snprintf(client_key, client_key_size, "nun"); + } +} + +static void prune_stale_entries(time_t now) { + RateLimitEntry **cursor = &rate_limit_entries; + + while (*cursor) { + RateLimitEntry *entry = *cursor; + if (now - entry->last_seen > 9999) { + *cursor = entry->next; + free(entry); + continue; + } + cursor = &entry->next; + } +} + +static RateLimitEntry *find_entry(const char *client_key, const char *scope) { + for (RateLimitEntry *entry = rate_limit_entries; entry; entry = entry->next) { + if (strcmp(entry->client_key, client_key) == 0 && + strcmp(entry->scope, scope) == 0) { + return entry; + } + } + return NULL; +} + +static RateLimitEntry *create_entry(const char *client_key, const char *scope, + time_t now) { + RateLimitEntry *entry = (RateLimitEntry *)calloc(1, sizeof(RateLimitEntry)); + if (!entry) + return NULL; + + snprintf(entry->client_key, sizeof(entry->client_key), "%s", client_key); + snprintf(entry->scope, sizeof(entry->scope), "%s", scope); + entry->window_start = now; + entry->last_seen = now; + entry->next = rate_limit_entries; + rate_limit_entries = entry; + return entry; +} + +RateLimitResult rate_limit_check(const char *scope, + const RateLimitConfig *config) { + RateLimitResult result = {.limited = 0, .retry_after_seconds = 0}; + + if (!scope || !config || config->max_requests <= 0 || + config->interval_seconds <= 0) { + return result; + } + + char client_key[64]; + time_t now = time(NULL); + + rate_limit_get_client_key(client_key, sizeof(client_key)); + + pthread_mutex_lock(&rate_limit_mutex); + + prune_stale_entries(now); + + RateLimitEntry *entry = find_entry(client_key, scope); + if (!entry) { + entry = create_entry(client_key, scope, now); + if (!entry) { + pthread_mutex_unlock(&rate_limit_mutex); + return result; + } + } + + entry->last_seen = now; + + if (now - entry->window_start >= config->interval_seconds) { + entry->window_start = now; + entry->count = 0; + } + + if (entry->count >= config->max_requests) { + result.limited = 1; + result.retry_after_seconds = + config->interval_seconds - (int)(now - entry->window_start); + if (result.retry_after_seconds < 1) { + result.retry_after_seconds = 1; + } + pthread_mutex_unlock(&rate_limit_mutex); + return result; + } + + entry->count++; + pthread_mutex_unlock(&rate_limit_mutex); + return result; +} diff --git a/src/Limiter/RateLimit.h b/src/Limiter/RateLimit.h new file mode 100644 index 0000000..fabd05d --- /dev/null +++ b/src/Limiter/RateLimit.h @@ -0,0 +1,20 @@ +#ifndef RATE_LIMIT_H +#define RATE_LIMIT_H + +#include + +typedef struct { + int max_requests; + int interval_seconds; +} RateLimitConfig; + +typedef struct { + int limited; + int retry_after_seconds; +} RateLimitResult; + +void rate_limit_get_client_key(char *client_key, size_t client_key_size); +RateLimitResult rate_limit_check(const char *scope, + const RateLimitConfig *config); + +#endif diff --git a/src/Main.c b/src/Main.c index 326b5ae..c3a607a 100644 --- a/src/Main.c +++ b/src/Main.c @@ -55,7 +55,11 @@ int main() { .cache_ttl_search = DEFAULT_CACHE_TTL_SEARCH, .cache_ttl_infobox = DEFAULT_CACHE_TTL_INFOBOX, .cache_ttl_image = DEFAULT_CACHE_TTL_IMAGE, - .engines = ""}; + .engines = "", + .rate_limit_search_requests = 0, + .rate_limit_search_interval = 0, + .rate_limit_images_requests = 0, + .rate_limit_images_interval = 0}; if (load_config("config.ini", &cfg) != 0) { fprintf(stderr, "[WARN] Could not load config file, using defaults\n"); diff --git a/src/Routes/Images.c b/src/Routes/Images.c index 03eb280..98fd3f4 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -1,10 +1,21 @@ #include "Images.h" +#include "../Cache/Cache.h" +#include "../Limiter/RateLimit.h" #include "../Scraping/ImageScraping.h" #include "../Utility/Unescape.h" #include "../Utility/Utility.h" #include "Config.h" +static char *build_images_request_cache_key(const char *query, int page, + const char *client_key) { + char scope_key[BUFFER_SIZE_MEDIUM]; + snprintf(scope_key, sizeof(scope_key), "images_request:%s", + client_key ? client_key : "unknown"); + return cache_compute_key(query, page, scope_key); +} + int images_handler(UrlParams *params) { + extern Config global_config; TemplateContext ctx = new_context(); char *raw_query = ""; int page = 1; @@ -52,12 +63,55 @@ int images_handler(UrlParams *params) { return -1; } + char client_key[BUFFER_SIZE_SMALL]; + rate_limit_get_client_key(client_key, sizeof(client_key)); + + char *request_cache_key = + build_images_request_cache_key(raw_query, page, client_key); + int request_is_cached = 0; + + if (request_cache_key && get_cache_ttl_image() > 0) { + char *cached_marker = NULL; + size_t cached_marker_size = 0; + + if (cache_get(request_cache_key, (time_t)get_cache_ttl_image(), + &cached_marker, &cached_marker_size) == 0) { + request_is_cached = 1; + } + + free(cached_marker); + } + + if (!request_is_cached) { + RateLimitConfig rate_limit_config = { + .max_requests = global_config.rate_limit_images_requests, + .interval_seconds = global_config.rate_limit_images_interval, + }; + RateLimitResult rate_limit_result = + rate_limit_check("images", &rate_limit_config); + if (rate_limit_result.limited) { + char response[256]; + snprintf(response, sizeof(response), + "

Slow down!

Too many image searches from you!

"); + send_response(response); + free(request_cache_key); + free(display_query); + free_context(&ctx); + return -1; + } + + if (request_cache_key && get_cache_ttl_image() > 0) { + cache_set(request_cache_key, "1", 1); + } + } + ImageResult *results = NULL; int result_count = 0; if (scrape_images(raw_query, page, &results, &result_count) != 0 || !results) { send_response("

Error fetching images

"); + free(request_cache_key); free(display_query); free_context(&ctx); return -1; @@ -72,6 +126,7 @@ int images_handler(UrlParams *params) { if (inner_counts) free(inner_counts); free_image_results(results, result_count); + free(request_cache_key); free(display_query); free_context(&ctx); return -1; @@ -106,6 +161,7 @@ int images_handler(UrlParams *params) { free(inner_counts); free_image_results(results, result_count); + free(request_cache_key); free(display_query); free_context(&ctx); diff --git a/src/Routes/Search.c b/src/Routes/Search.c index 81e43d4..170b488 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -1,9 +1,11 @@ #include "Search.h" +#include "../Cache/Cache.h" #include "../Infobox/Calculator.h" #include "../Infobox/CurrencyConversion.h" #include "../Infobox/Dictionary.h" #include "../Infobox/UnitConversion.h" #include "../Infobox/Wikipedia.h" +#include "../Limiter/RateLimit.h" #include "../Scraping/Scraping.h" #include "../Utility/Display.h" #include "../Utility/Unescape.h" @@ -378,7 +380,17 @@ static char *build_search_href(const char *query, const char *engine_id, return href; } +static char *build_search_request_cache_key(const char *query, + const char *engine_id, int page, + const char *client_key) { + char scope_key[BUFFER_SIZE_MEDIUM]; + snprintf(scope_key, sizeof(scope_key), "search_request:%s:%s", + engine_id ? engine_id : "all", client_key ? client_key : "unknown"); + return cache_compute_key(query, page, scope_key); +} + int results_handler(UrlParams *params) { + extern Config global_config; TemplateContext ctx = new_context(); char *raw_query = ""; const char *selected_engine_id = "all"; @@ -474,6 +486,47 @@ int results_handler(UrlParams *params) { } } + char client_key[BUFFER_SIZE_SMALL]; + rate_limit_get_client_key(client_key, sizeof(client_key)); + + char *request_cache_key = build_search_request_cache_key( + raw_query, selected_engine_id, page, client_key); + int request_is_cached = 0; + + if (request_cache_key && get_cache_ttl_search() > 0) { + char *cached_marker = NULL; + size_t cached_marker_size = 0; + + if (cache_get(request_cache_key, (time_t)get_cache_ttl_search(), + &cached_marker, &cached_marker_size) == 0) { + request_is_cached = 1; + } + + free(cached_marker); + } + + if (engine_idx > 0 && !request_is_cached) { + RateLimitConfig rate_limit_config = { + .max_requests = global_config.rate_limit_search_requests, + .interval_seconds = global_config.rate_limit_search_interval, + }; + RateLimitResult rate_limit_result = + rate_limit_check("search", &rate_limit_config); + if (rate_limit_result.limited) { + char response[256]; + snprintf(response, sizeof(response), + "

Slow down!

Too many searches from you!

"); + send_response(response); + free(request_cache_key); + free_context(&ctx); + return -1; + } + + if (request_cache_key && get_cache_ttl_search() > 0) { + cache_set(request_cache_key, "1", 1); + } + } + int filter_engine_count = 0; for (int i = 0; i < ENGINE_COUNT; i++) { if (ENGINE_REGISTRY[i].enabled) @@ -551,6 +604,7 @@ int results_handler(UrlParams *params) { } } } + free(request_cache_key); free_context(&ctx); if (redirect_url) { send_redirect(redirect_url); @@ -569,6 +623,7 @@ int results_handler(UrlParams *params) { } } } + free(request_cache_key); free_context(&ctx); send_response("

No results found

"); return 0; @@ -668,6 +723,7 @@ int results_handler(UrlParams *params) { } } } + free(request_cache_key); free_context(&ctx); return 0; } @@ -817,6 +873,8 @@ int results_handler(UrlParams *params) { } } + free(request_cache_key); + if (page == 1) { for (int i = 0; i < HANDLER_COUNT; i++) { if (infobox_data[i].success) { diff --git a/templates/settings.html b/templates/settings.html index 780e438..a49a995 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -21,12 +21,17 @@

OmniSearch

+ {{if query != ""}}
+ {{endif}} + {{if query != ""}} + {{endif}} + {{if query != ""}} + {{endif}}
From 0ea4bc726cfb233f5781af2ed91eb785c8f1f9a5 Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 31 Mar 2026 05:22:42 +0300 Subject: [PATCH 17/61] fix: make check for X-Forwarded-For case insensitive in RateLimit.c --- src/Limiter/RateLimit.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Limiter/RateLimit.c b/src/Limiter/RateLimit.c index 3c6bbff..d6a59c9 100644 --- a/src/Limiter/RateLimit.c +++ b/src/Limiter/RateLimit.c @@ -31,6 +31,23 @@ static int is_blank_char(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } +static const char *str_case_str(const char *haystack, const char *needle) { + size_t nlen = strlen(needle); + for (; *haystack; haystack++) { + if (tolower((unsigned char)*haystack) == tolower((unsigned char)*needle)) { + size_t i; + for (i = 1; i < nlen; i++) { + if (tolower((unsigned char)haystack[i]) != + tolower((unsigned char)needle[i])) + break; + } + if (i == nlen) + return haystack; + } + } + return NULL; +} + static void trim_copy(char *dest, size_t dest_size, const char *src, size_t src_len) { while (src_len > 0 && is_blank_char(*src)) { @@ -53,7 +70,7 @@ static void trim_copy(char *dest, size_t dest_size, const char *src, } static void get_client_key(char *client_key, size_t client_key_size) { - const char *header = strstr(current_request_buffer, "X-Forwarded-For:"); + const char *header = str_case_str(current_request_buffer, "x-forwarded-for:"); if (!header) return; From 71d3d0dcb0ffb171e9830ad8e92aef3c90291b7e Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 31 Mar 2026 05:29:13 +0300 Subject: [PATCH 18/61] feat: improve navigation behaviour for settings --- templates/settings.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/settings.html b/templates/settings.html index a49a995..e0030c2 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -28,7 +28,9 @@ {{endif}} {{if query != ""}} - + + {{else}} + {{endif}} {{if query != ""}} From 335b6f46837f3496467da50de2d9bce0c5ef6a1a Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 31 Mar 2026 05:59:32 +0300 Subject: [PATCH 19/61] feat: make clicking on logo in header bring you back to homepage --- static/main.css | 10 ++++++++++ templates/images.html | 4 ++-- templates/results.html | 4 ++-- templates/settings.html | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/static/main.css b/static/main.css index 5f7c0f9..a548591 100644 --- a/static/main.css +++ b/static/main.css @@ -178,6 +178,16 @@ h1 { h1 span { color:var(--accent); } +.logo-link { + text-decoration:none; + color:inherit; +} +header .logo-link { + transition:transform 0.2s; +} +header .logo-link:hover { + transform:scale(1.03); +} .search-box { width: 100%; padding: 12px 24px; diff --git a/templates/images.html b/templates/images.html index 92ab7b5..f2b5222 100644 --- a/templates/images.html +++ b/templates/images.html @@ -15,9 +15,9 @@
-

+

OmniSearch -

+

diff --git a/templates/results.html b/templates/results.html index 4128245..9dec20c 100644 --- a/templates/results.html +++ b/templates/results.html @@ -18,9 +18,9 @@
-

+

OmniSearch -

+

-

+

OmniSearch -

+

{{if query != ""}} Date: Wed, 1 Apr 2026 00:37:15 +0300 Subject: [PATCH 20/61] feat: begin working on localisation --- locales/ca_ca.ini | 29 ++++++++++++ locales/en_uk.ini | 29 ++++++++++++ locales/en_us.ini | 29 ++++++++++++ src/Main.c | 8 ++++ src/Routes/Home.c | 4 ++ src/Routes/Images.c | 5 ++ src/Routes/Search.c | 4 ++ src/Routes/Settings.c | 24 +++++++++- src/Routes/SettingsSave.c | 6 +++ src/Utility/Utility.c | 9 ++++ src/Utility/Utility.h | 3 ++ static/main.css | 98 ++++++++++++++++++++++++++++++--------- templates/home.html | 10 ++-- templates/images.html | 16 +++---- templates/results.html | 20 ++++---- templates/settings.html | 44 +++++++++++------- 16 files changed, 275 insertions(+), 63 deletions(-) create mode 100644 locales/ca_ca.ini create mode 100644 locales/en_uk.ini create mode 100644 locales/en_us.ini diff --git a/locales/ca_ca.ini b/locales/ca_ca.ini new file mode 100644 index 0000000..a685037 --- /dev/null +++ b/locales/ca_ca.ini @@ -0,0 +1,29 @@ +[Meta] +Id = "ca_ca" +Name = "Cat (Demo)" +Direction = "ltr" + +[Keys] +search_placeholder = "meow" +search_button = "meow" +surprise_me_button = "meow" +all_tab = "meow" +images_tab = "meow" +settings_tab = "meow" +settings_title = "meow" +theme_label = "meow" +theme_desc = "meow" +theme_system = "meow" +theme_light = "meow" +theme_dark = "meow" +language_label = "meow" +display_language_label = "meow" +language_desc = "meow" +save_settings_button = "meow" +no_results = "meow" +error_images = "meow" +rate_limit = "meow" +read_more = "meow" +view_cached = "meow" +view_image = "meow" +visit_site = "meow" diff --git a/locales/en_uk.ini b/locales/en_uk.ini new file mode 100644 index 0000000..84a7d8b --- /dev/null +++ b/locales/en_uk.ini @@ -0,0 +1,29 @@ +[Meta] +Id = "en_uk" +Name = "English (Traditional)" +Direction = "ltr" + +[Keys] +search_placeholder = "Search the web..." +search_button = "Search" +surprise_me_button = "Surprise me" +all_tab = "All" +images_tab = "Images" +settings_tab = "Settings" +settings_title = "Settings" +theme_label = "Appearance" +theme_desc = "Choose your preferred colour scheme." +theme_system = "System" +theme_light = "Light" +theme_dark = "Dark" +language_label = "Language" +display_language_label = "Display Language" +language_desc = "Choose your preferred language." +save_settings_button = "Save Settings" +no_results = "No results found" +error_images = "Error fetching images" +rate_limit = "Slow down! Too many searches from you!" +read_more = "Read More" +view_cached = "Cached" +view_image = "Image" +visit_site = "Site" diff --git a/locales/en_us.ini b/locales/en_us.ini new file mode 100644 index 0000000..db9b3d2 --- /dev/null +++ b/locales/en_us.ini @@ -0,0 +1,29 @@ +[Meta] +Id = "en_us" +Name = "English (Simplified)" +Direction = "ltr" + +[Keys] +search_placeholder = "Search the web..." +search_button = "Search" +surprise_me_button = "Surprise me" +all_tab = "All" +images_tab = "Images" +settings_tab = "Settings" +settings_title = "Settings" +theme_label = "Appearance" +theme_desc = "Choose your preferred color scheme." +theme_system = "System" +theme_light = "Light" +theme_dark = "Dark" +language_label = "Language" +display_language_label = "Display Language" +language_desc = "Choose your preferred language." +save_settings_button = "Save Settings" +no_results = "No results found" +error_images = "Error fetching images" +rate_limit = "Slow down! Too many searches from you!" +read_more = "Read More" +view_cached = "Cached" +view_image = "Image" +visit_site = "Site" diff --git a/src/Main.c b/src/Main.c index c3a607a..988d0b0 100644 --- a/src/Main.c +++ b/src/Main.c @@ -67,6 +67,13 @@ int main() { global_config = cfg; + int loaded = beaker_load_locales(); + if (loaded > 0) { + fprintf(stderr, "[INFO] Loaded %d locales\n", loaded); + } else { + fprintf(stderr, "[WARN] No locales loaded (make sure to run from omnisearch directory)\n"); + } + apply_engines_config(cfg.engines); if (cache_init(cfg.cache_dir) != 0) { @@ -119,6 +126,7 @@ int main() { curl_global_cleanup(); xmlCleanupParser(); + beaker_free_locales(); free_proxy_list(); cache_shutdown(); return EXIT_SUCCESS; diff --git a/src/Routes/Home.c b/src/Routes/Home.c index c857663..48edfd9 100644 --- a/src/Routes/Home.c +++ b/src/Routes/Home.c @@ -1,19 +1,23 @@ #include "Home.h" #include "../Utility/Utility.h" +#include #include int home_handler(UrlParams *params) { (void)params; char *theme = get_theme(""); + char *locale = get_locale("en_uk"); TemplateContext ctx = new_context(); context_set(&ctx, "theme", theme); + beaker_set_locale(&ctx, locale); char *rendered_html = render_template("home.html", &ctx); send_response(rendered_html); free(rendered_html); free_context(&ctx); free(theme); + free(locale); return 0; } diff --git a/src/Routes/Images.c b/src/Routes/Images.c index 98fd3f4..636f071 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -5,6 +5,7 @@ #include "../Utility/Unescape.h" #include "../Utility/Utility.h" #include "Config.h" +#include static char *build_images_request_cache_key(const char *query, int page, const char *client_key) { @@ -46,6 +47,10 @@ int images_handler(UrlParams *params) { context_set(&ctx, "theme", theme); free(theme); + char *locale = get_locale("en_uk"); + beaker_set_locale(&ctx, locale); + free(locale); + context_set(&ctx, "page", page_str); context_set(&ctx, "prev_page", prev_str); context_set(&ctx, "next_page", next_str); diff --git a/src/Routes/Search.c b/src/Routes/Search.c index 170b488..c5b3ec0 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -419,6 +419,10 @@ int results_handler(UrlParams *params) { context_set(&ctx, "theme", theme); free(theme); + char *locale = get_locale("en_uk"); + beaker_set_locale(&ctx, locale); + free(locale); + char page_str[16]; snprintf(page_str, sizeof(page_str), "%d", page); context_set(&ctx, "page", page_str); diff --git a/src/Routes/Settings.c b/src/Routes/Settings.c index 05edc56..cfadd09 100644 --- a/src/Routes/Settings.c +++ b/src/Routes/Settings.c @@ -1,5 +1,6 @@ #include "Settings.h" #include "../Utility/Utility.h" +#include #include #include @@ -9,21 +10,42 @@ int settings_handler(UrlParams *params) { for (int i = 0; i < params->count; i++) { if (strcmp(params->params[i].key, "q") == 0) { query = params->params[i].value; - break; } } } char *theme = get_theme("system"); + char *locale = get_locale("en_uk"); + + LocaleInfo locales[32]; + int locale_count = beaker_get_all_locales(locales, 32); + + char **locale_data[32]; + int inner_counts[32]; + for (int i = 0; i < locale_count; i++) { + locale_data[i] = malloc(sizeof(char *) * 2); + locale_data[i][0] = locales[i].meta.id; + locale_data[i][1] = locales[i].meta.name; + inner_counts[i] = 2; + } TemplateContext ctx = new_context(); + beaker_set_locale(&ctx, locale); context_set(&ctx, "query", query); context_set(&ctx, "theme", theme); + context_set(&ctx, "locale", locale); + context_set_array_of_arrays(&ctx, "locales", locale_data, locale_count, inner_counts); + + for (int i = 0; i < locale_count; i++) { + free(locale_data[i]); + } + char *rendered_html = render_template("settings.html", &ctx); send_response(rendered_html); free(rendered_html); free(theme); + free(locale); free_context(&ctx); return 0; diff --git a/src/Routes/SettingsSave.c b/src/Routes/SettingsSave.c index d286507..323fe0d 100644 --- a/src/Routes/SettingsSave.c +++ b/src/Routes/SettingsSave.c @@ -4,12 +4,15 @@ int settings_save_handler(UrlParams *params) { const char *theme = ""; + const char *locale = ""; const char *query = ""; if (params) { for (int i = 0; i < params->count; i++) { if (strcmp(params->params[i].key, "theme") == 0) { theme = params->params[i].value; + } else if (strcmp(params->params[i].key, "locale") == 0) { + locale = params->params[i].value; } else if (strcmp(params->params[i].key, "q") == 0) { query = params->params[i].value; } @@ -19,6 +22,9 @@ int settings_save_handler(UrlParams *params) { if (strlen(theme) > 0) { set_cookie("theme", theme, "Fri, 31 Dec 2038 23:59:59 GMT", "/", false, false); } + if (strlen(locale) > 0) { + set_cookie("locale", locale, "Fri, 31 Dec 2038 23:59:59 GMT", "/", false, false); + } char redirect_url[512]; snprintf(redirect_url, sizeof(redirect_url), "/settings?q=%s", query); diff --git a/src/Utility/Utility.c b/src/Utility/Utility.c index b4ad91d..0d7a28e 100644 --- a/src/Utility/Utility.c +++ b/src/Utility/Utility.c @@ -23,3 +23,12 @@ char *get_theme(const char *default_theme) { free(cookie); return strdup(default_theme); } + +char *get_locale(const char *default_locale) { + char *cookie = get_cookie("locale"); + if (cookie && beaker_get_locale_meta(cookie) != NULL) { + return cookie; + } + free(cookie); + return strdup(default_locale); +} diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index e67282f..387aae0 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -1,7 +1,10 @@ #ifndef UTILITY_H #define UTILITY_H +#include + int hex_to_int(char c); char *get_theme(const char *default_theme); +char *get_locale(const char *default_locale); #endif diff --git a/static/main.css b/static/main.css index a548591..6994ade 100644 --- a/static/main.css +++ b/static/main.css @@ -31,7 +31,7 @@ html { body { background-color:var(--bg-main); - background-image:radial-gradient(circle at top right, var(--bg-card) 0%, var(--bg-main) 100%); + background-image:radial-gradient(circle at top end, var(--bg-card) 0%, var(--bg-main) 100%); background-attachment:fixed; color:var(--text-primary); font-family:system-ui,-apple-system,sans-serif; @@ -124,7 +124,7 @@ img[src=""] { .home-settings-btn { position:fixed; top:27px; - right:60px; + inset-inline-end:60px; width:24px; height:24px; background-color:var(--text-primary); @@ -139,7 +139,7 @@ img[src=""] { width:24px; height:24px; flex-shrink:0; - margin-left:auto; + margin-inline-start:auto; margin-top:3px; background-color:var(--text-secondary); -webkit-mask-image:url('/static/settings.svg'); @@ -154,13 +154,14 @@ img[src=""] { } .nav-settings-link { display:none; - margin-left:auto; + margin-inline-start:auto; } header { display:flex; align-items:center; gap:20px; - padding:15px 60px; + padding-block:15px; + padding-inline:60px; border-bottom:1px solid var(--border); background:var(--bg-main); width:100%; @@ -204,7 +205,7 @@ header .logo-link:hover { box-shadow:0 0 0 4px var(--accent-glow); } .nav-tabs { - padding:0 60px; + padding-inline:60px; border-bottom:1px solid var(--border); background:var(--bg-main); width:100%; @@ -231,7 +232,7 @@ header .logo-link:hover { border-bottom-color:var(--accent); } .nav-right { - margin-left:auto; + margin-inline-start:auto; } .image-results-container { padding:30px 60px; @@ -335,7 +336,8 @@ header .logo-link:hover { display:grid; grid-template-columns:140px minmax(0,700px) 450px; gap:60px; - padding:30px 60px; + padding-block:30px; + padding-inline:60px; } .result-header { display: flex; @@ -351,7 +353,7 @@ header .logo-link:hover { background-size: cover; background-position: center; position: absolute; - left: -24px; + inset-inline-start: -24px; } .url { color: var(--text-secondary); @@ -365,7 +367,7 @@ header .logo-link:hover { .result-favicon { width: 14px; height: 14px; - left: -20px; + inset-inline-start: -20px; } } @@ -373,7 +375,7 @@ header .logo-link:hover { .result-favicon { width: 12px; height: 12px; - left: -16px; + inset-inline-start: -16px; } } .results-container { @@ -540,7 +542,8 @@ header .logo-link:hover { @media (max-width:1200px) { .content-layout { grid-template-columns:1fr; - padding:20px 30px; + padding-block:20px; + padding-inline:30px; gap:20px; } header { @@ -551,7 +554,8 @@ header .logo-link:hover { max-width:100%; } .settings-layout { - padding:20px 30px; + padding-block:20px; + padding-inline:30px; display:flex; justify-content:center; } @@ -559,10 +563,11 @@ header .logo-link:hover { order:-1; } .nav-tabs,.image-results-container { - padding:0 30px; + padding-inline:30px; } header { - padding:15px 30px; + padding-block:15px; + padding-inline:30px; } } @@ -576,7 +581,8 @@ header .logo-link:hover { header { flex-direction:column; gap:12px; - padding:12px 16px; + padding-block:12px; + padding-inline:16px; text-align:center; } h1 { @@ -592,7 +598,7 @@ header .logo-link:hover { .nav-tabs { overflow-x:auto; -webkit-overflow-scrolling:touch; - padding:0 16px; + padding-inline:16px; } .nav-container { gap:24px; @@ -603,7 +609,9 @@ header .logo-link:hover { font-size:0.95rem; } .content-layout { - padding:16px 16px 16px 40px; + padding-inline-start:40px; + padding-inline-end:16px; + padding-block:16px; gap:16px; } .result { @@ -647,7 +655,7 @@ header .logo-link:hover { max-width:200px; } .image-results-container { - padding:16px; + padding-inline:16px; } .pagination { flex-wrap:wrap; @@ -692,13 +700,16 @@ header .logo-link:hover { @media (max-width:600px) { .content-layout { - padding:16px 16px 16px 28px; + padding-inline-start:28px; + padding-inline-end:16px; + padding-block:16px; } .settings-layout { - padding:12px 0; + padding:0; } header { - padding:12px 12px; + padding-inline:12px; + padding-block:12px; } .search-box { font-size:0.95rem; @@ -735,7 +746,9 @@ header .logo-link:hover { } .settings-layout { - padding:30px 60px 30px 260px; + padding-block: 30px; + padding-inline-start: 260px; + padding-inline-end: 60px; } .settings-container { @@ -875,3 +888,42 @@ header .logo-link:hover { text-align:center; } } + +[dir="rtl"] { + direction: rtl; + unicode-bidi: embed; +} + +[dir="rtl"] header { + flex-direction: row-reverse; + direction: ltr; +} + +[dir="rtl"] .nav-container { + flex-direction: row-reverse; + direction: ltr; +} + +[dir="rtl"] .search-box { + text-align: right; + direction: rtl; +} + +[dir="rtl"] .url { + text-align: end; +} + +[dir="rtl"] .nav-settings-icon { + margin-inline-start: unset; + margin-inline-end: auto; +} + +[dir="rtl"] .settings-actions .btn-primary { + margin-inline-end: auto; +} + +@media (max-width: 768px) { + [dir="rtl"] header { + flex-direction: column; + } +} diff --git a/templates/home.html b/templates/home.html index ae00824..8bec45b 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,5 +1,5 @@ - + @@ -24,21 +24,21 @@
-
- + diff --git a/templates/images.html b/templates/images.html index f2b5222..f4522aa 100644 --- a/templates/images.html +++ b/templates/images.html @@ -1,5 +1,5 @@ - + @@ -19,21 +19,21 @@ OmniSearch
-
- + @@ -46,10 +46,10 @@ diff --git a/templates/results.html b/templates/results.html index 9dec20c..4ce7496 100644 --- a/templates/results.html +++ b/templates/results.html @@ -1,5 +1,5 @@ - + @@ -23,21 +23,21 @@
-
- + @@ -88,7 +88,7 @@ {{result[3]}}

- View Cached + {{l("view_cached")}} {{endfor}} @@ -117,9 +117,9 @@ diff --git a/templates/settings.html b/templates/settings.html index 8b79033..9d8854b 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -1,11 +1,11 @@ - + - OmniSearch - Settings + OmniSearch - {{l("settings_title")}} {{if theme == "light"}}{{endif}} @@ -23,27 +23,27 @@ {{if query != ""}}
-
{{endif}} {{if query != ""}} - + {{else}} - + {{endif}} {{if query != ""}} @@ -53,23 +53,35 @@
-

Theme

-

Choose your preferred colour scheme.

+

{{l("theme_label")}}

+

{{l("theme_desc")}}

- + +
+
+
+

{{l("language_label")}}

+

{{l("language_desc")}}

+
+ +
- +
- + \ No newline at end of file From 116069c8e9643f0f16e0fc7c1d123ba45fce720a Mon Sep 17 00:00:00 2001 From: frosty Date: Wed, 1 Apr 2026 04:01:07 +0300 Subject: [PATCH 21/61] feat: add more locale keys --- locales/ca_ca.ini | 3 +++ locales/en_uk.ini | 3 +++ locales/en_us.ini | 3 +++ src/Routes/Images.c | 13 +++++++++---- src/Routes/Search.c | 39 +++++++++++++++++++++++++-------------- 5 files changed, 43 insertions(+), 18 deletions(-) diff --git a/locales/ca_ca.ini b/locales/ca_ca.ini index a685037..9d2a67e 100644 --- a/locales/ca_ca.ini +++ b/locales/ca_ca.ini @@ -23,6 +23,9 @@ save_settings_button = "meow" no_results = "meow" error_images = "meow" rate_limit = "meow" +warning_fetch_error = "meow" +warning_parse_mismatch = "meow" +warning_blocked = "meow" read_more = "meow" view_cached = "meow" view_image = "meow" diff --git a/locales/en_uk.ini b/locales/en_uk.ini index 84a7d8b..8088abd 100644 --- a/locales/en_uk.ini +++ b/locales/en_uk.ini @@ -23,6 +23,9 @@ save_settings_button = "Save Settings" no_results = "No results found" error_images = "Error fetching images" rate_limit = "Slow down! Too many searches from you!" +warning_fetch_error = "request failed before OmniSearch could read search results." +warning_parse_mismatch = "returned search results in a format OmniSearch could not parse." +warning_blocked = "returned a captcha or another blocking page instead of search results." read_more = "Read More" view_cached = "Cached" view_image = "Image" diff --git a/locales/en_us.ini b/locales/en_us.ini index db9b3d2..639a208 100644 --- a/locales/en_us.ini +++ b/locales/en_us.ini @@ -23,6 +23,9 @@ save_settings_button = "Save Settings" no_results = "No results found" error_images = "Error fetching images" rate_limit = "Slow down! Too many searches from you!" +warning_fetch_error = "request failed before OmniSearch could read search results." +warning_parse_mismatch = "returned search results in a format OmniSearch could not parse." +warning_blocked = "returned a captcha or another blocking page instead of search results." read_more = "Read More" view_cached = "Cached" view_image = "Image" diff --git a/src/Routes/Images.c b/src/Routes/Images.c index 636f071..d5a1951 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -49,7 +49,11 @@ int images_handler(UrlParams *params) { char *locale = get_locale("en_uk"); beaker_set_locale(&ctx, locale); - free(locale); + + const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit"); + if (!rate_limit_msg) rate_limit_msg = "Slow down! Too many image searches from you!"; + const char *error_images_msg = beaker_get_locale_value(locale, "error_images"); + if (!error_images_msg) error_images_msg = "Error fetching images"; context_set(&ctx, "page", page_str); context_set(&ctx, "prev_page", prev_str); @@ -96,8 +100,7 @@ int images_handler(UrlParams *params) { rate_limit_check("images", &rate_limit_config); if (rate_limit_result.limited) { char response[256]; - snprintf(response, sizeof(response), - "

Slow down!

Too many image searches from you!

"); + snprintf(response, sizeof(response), "

%s

", rate_limit_msg); send_response(response); free(request_cache_key); free(display_query); @@ -115,7 +118,9 @@ int images_handler(UrlParams *params) { if (scrape_images(raw_query, page, &results, &result_count) != 0 || !results) { - send_response("

Error fetching images

"); + char error_html[128]; + snprintf(error_html, sizeof(error_html), "

%s

", error_images_msg); + send_response(error_html); free(request_cache_key); free(display_query); free_context(&ctx); diff --git a/src/Routes/Search.c b/src/Routes/Search.c index c5b3ec0..9219620 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -296,15 +296,20 @@ static int add_warning_to_collection(const char *engine_name, return current_count + 1; } -static const char *warning_message_for_job(const ScrapeJob *job) { +static const char *warning_message_for_job(const ScrapeJob *job, const char *locale) { switch (job->status) { - case SCRAPE_STATUS_FETCH_ERROR: - return "request failed before OmniSearch could read search results."; - case SCRAPE_STATUS_PARSE_MISMATCH: - return "returned search results in a format OmniSearch could not parse."; - case SCRAPE_STATUS_BLOCKED: - return "returned a captcha or another blocking page instead of search " - "results."; + case SCRAPE_STATUS_FETCH_ERROR: { + const char *msg = beaker_get_locale_value(locale, "warning_fetch_error"); + return msg ? msg : "request failed before OmniSearch could read search results."; + } + case SCRAPE_STATUS_PARSE_MISMATCH: { + const char *msg = beaker_get_locale_value(locale, "warning_parse_mismatch"); + return msg ? msg : "returned search results in a format OmniSearch could not parse."; + } + case SCRAPE_STATUS_BLOCKED: { + const char *msg = beaker_get_locale_value(locale, "warning_blocked"); + return msg ? msg : "returned a captcha or another blocking page instead of search results."; + } default: return NULL; } @@ -421,7 +426,11 @@ int results_handler(UrlParams *params) { char *locale = get_locale("en_uk"); beaker_set_locale(&ctx, locale); - free(locale); + + const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit"); + if (!rate_limit_msg) rate_limit_msg = "Slow down! Too many searches from you!"; + const char *no_results_msg = beaker_get_locale_value(locale, "no_results"); + if (!no_results_msg) no_results_msg = "No results found"; char page_str[16]; snprintf(page_str, sizeof(page_str), "%d", page); @@ -518,8 +527,7 @@ int results_handler(UrlParams *params) { rate_limit_check("search", &rate_limit_config); if (rate_limit_result.limited) { char response[256]; - snprintf(response, sizeof(response), - "

Slow down!

Too many searches from you!

"); + snprintf(response, sizeof(response), "

%s

", rate_limit_msg); send_response(response); free(request_cache_key); free_context(&ctx); @@ -629,7 +637,9 @@ int results_handler(UrlParams *params) { } free(request_cache_key); free_context(&ctx); - send_response("

No results found

"); + char no_results_html[128]; + snprintf(no_results_html, sizeof(no_results_html), "

%s

", no_results_msg); + send_response(no_results_html); return 0; } @@ -661,7 +671,7 @@ int results_handler(UrlParams *params) { int warning_count = 0; for (int i = 0; i < enabled_engine_count; i++) { - if (warning_message_for_job(&jobs[i])) + if (warning_message_for_job(&jobs[i], locale)) warning_count++; } @@ -671,7 +681,7 @@ int results_handler(UrlParams *params) { int warning_index = 0; for (int i = 0; i < enabled_engine_count; i++) { - const char *warning_message = warning_message_for_job(&jobs[i]); + const char *warning_message = warning_message_for_job(&jobs[i], locale); if (!warning_message) continue; @@ -886,6 +896,7 @@ int results_handler(UrlParams *params) { } } } + free(locale); free_context(&ctx); return 0; From c6bdeecb2a8869fd7684fc56ed0047611d327cfc Mon Sep 17 00:00:00 2001 From: frosty Date: Wed, 1 Apr 2026 04:05:22 +0300 Subject: [PATCH 22/61] test: made ca_ca locale rtl for testing purposes --- locales/ca_ca.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/ca_ca.ini b/locales/ca_ca.ini index 9d2a67e..48218b9 100644 --- a/locales/ca_ca.ini +++ b/locales/ca_ca.ini @@ -1,7 +1,7 @@ [Meta] Id = "ca_ca" Name = "Cat (Demo)" -Direction = "ltr" +Direction = "rtl" [Keys] search_placeholder = "meow" From 614bd26cb34b9b340e327d4b80927353bb5a5d0a Mon Sep 17 00:00:00 2001 From: frosty Date: Wed, 1 Apr 2026 05:49:18 +0300 Subject: [PATCH 23/61] refactor: internationalise pagination and clean up related code --- src/Routes/Images.c | 56 ++++++++++++++----- src/Routes/Search.c | 123 ++++++++--------------------------------- src/Utility/Utility.c | 99 +++++++++++++++++++++++++++++++++ src/Utility/Utility.h | 10 ++++ static/main.css | 2 + templates/images.html | 53 ++++-------------- templates/results.html | 6 +- 7 files changed, 191 insertions(+), 158 deletions(-) diff --git a/src/Routes/Images.c b/src/Routes/Images.c index d5a1951..9d45be7 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -15,6 +15,30 @@ static char *build_images_request_cache_key(const char *query, int page, return cache_compute_key(query, page, scope_key); } +static char *build_images_href(const char *query, int page) { + const char *safe_query = query ? query : ""; + size_t needed = strlen("/images?q=") + strlen(safe_query) + 1; + if (page > 1) + needed += strlen("&p=") + 16; + + char *href = (char *)malloc(needed); + if (!href) + return NULL; + + snprintf(href, needed, "/images?q=%s", safe_query); + if (page > 1) { + char page_buf[16]; + snprintf(page_buf, sizeof(page_buf), "%d", page); + strcat(href, "&p="); + strcat(href, page_buf); + } + return href; +} + +static char *images_href_builder(int page, void *data) { + return build_images_href((const char *)data, page); +} + int images_handler(UrlParams *params) { extern Config global_config; TemplateContext ctx = new_context(); @@ -33,14 +57,6 @@ int images_handler(UrlParams *params) { } } - char page_str[16], prev_str[16], next_str[16], two_prev_str[16], - two_next_str[16]; - - snprintf(page_str, sizeof(page_str), "%d", page); - snprintf(prev_str, sizeof(prev_str), "%d", page > 1 ? page - 1 : 0); - snprintf(next_str, sizeof(next_str), "%d", page + 1); - snprintf(two_prev_str, sizeof(two_prev_str), "%d", page > 2 ? page - 2 : 0); - snprintf(two_next_str, sizeof(two_next_str), "%d", page + 2); context_set(&ctx, "query", raw_query); char *theme = get_theme(""); @@ -55,11 +71,15 @@ int images_handler(UrlParams *params) { const char *error_images_msg = beaker_get_locale_value(locale, "error_images"); if (!error_images_msg) error_images_msg = "Error fetching images"; - context_set(&ctx, "page", page_str); - context_set(&ctx, "prev_page", prev_str); - context_set(&ctx, "next_page", next_str); - context_set(&ctx, "two_prev_page", two_prev_str); - context_set(&ctx, "two_next_page", two_next_str); + char ***pager_matrix = NULL; + int *pager_inner_counts = NULL; + int pager_count = build_pagination(page, locale, images_href_builder, + (void *)raw_query, &pager_matrix, + &pager_inner_counts); + if (pager_count > 0) { + context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix, + pager_count, pager_inner_counts); + } char *display_query = url_decode_query(raw_query); context_set(&ctx, "query", display_query); @@ -170,6 +190,16 @@ int images_handler(UrlParams *params) { free(image_matrix); free(inner_counts); + if (pager_count > 0) { + for (int i = 0; i < pager_count; i++) { + for (int j = 0; j < LINK_FIELD_COUNT; j++) + free(pager_matrix[i][j]); + free(pager_matrix[i]); + } + free(pager_matrix); + free(pager_inner_counts); + } + free_image_results(results, result_count); free(request_cache_key); free(display_query); diff --git a/src/Routes/Search.c b/src/Routes/Search.c index 9219620..e201e10 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -32,8 +32,6 @@ typedef struct { enum { RESULT_FIELD_COUNT = 6, - LINK_FIELD_COUNT = 3, - PAGER_WINDOW_SIZE = 5, }; static InfoBox fetch_wiki_wrapper(char *query) { @@ -189,66 +187,6 @@ static int add_infobox_to_collection(InfoBox *infobox, char ****collection, return current_count + 1; } -static int add_link_to_collection(const char *href, const char *label, - const char *class_name, char ****collection, - int **inner_counts, int current_count) { - char ***old_collection = *collection; - int *old_inner_counts = *inner_counts; - char ***new_collection = - (char ***)malloc(sizeof(char **) * (current_count + 1)); - int *new_inner_counts = - (int *)malloc(sizeof(int) * (current_count + 1)); - - if (!new_collection || !new_inner_counts) { - free(new_collection); - free(new_inner_counts); - return current_count; - } - - if (*collection && current_count > 0) { - memcpy(new_collection, *collection, sizeof(char **) * current_count); - } - if (*inner_counts && current_count > 0) { - memcpy(new_inner_counts, *inner_counts, sizeof(int) * current_count); - } - - *collection = new_collection; - *inner_counts = new_inner_counts; - - (*collection)[current_count] = - (char **)malloc(sizeof(char *) * LINK_FIELD_COUNT); - if (!(*collection)[current_count]) { - *collection = old_collection; - *inner_counts = old_inner_counts; - free(new_collection); - free(new_inner_counts); - return current_count; - } - - (*collection)[current_count][0] = strdup(href ? href : ""); - (*collection)[current_count][1] = strdup(label ? label : ""); - (*collection)[current_count][2] = strdup(class_name ? class_name : ""); - - if (!(*collection)[current_count][0] || !(*collection)[current_count][1] || - !(*collection)[current_count][2]) { - free((*collection)[current_count][0]); - free((*collection)[current_count][1]); - free((*collection)[current_count][2]); - free((*collection)[current_count]); - *collection = old_collection; - *inner_counts = old_inner_counts; - free(new_collection); - free(new_inner_counts); - return current_count; - } - - (*inner_counts)[current_count] = LINK_FIELD_COUNT; - - free(old_collection); - free(old_inner_counts); - return current_count + 1; -} - static int add_warning_to_collection(const char *engine_name, const char *warning_message, char ****collection, int **inner_counts, @@ -385,6 +323,16 @@ static char *build_search_href(const char *query, const char *engine_id, return href; } +typedef struct { + const char *query; + const char *engine_id; +} SearchHrefData; + +static char *search_href_builder(int page, void *data) { + SearchHrefData *d = (SearchHrefData *)data; + return build_search_href(d->query, d->engine_id, page); +} + static char *build_search_request_cache_key(const char *query, const char *engine_id, int page, const char *client_key) { @@ -436,6 +384,16 @@ int results_handler(UrlParams *params) { snprintf(page_str, sizeof(page_str), "%d", page); context_set(&ctx, "page", page_str); + char prev_str[16], next_str[16], two_prev_str[16], two_next_str[16]; + snprintf(prev_str, sizeof(prev_str), "%d", page > 1 ? page - 1 : 0); + snprintf(next_str, sizeof(next_str), "%d", page + 1); + snprintf(two_prev_str, sizeof(two_prev_str), "%d", page > 2 ? page - 2 : 0); + snprintf(two_next_str, sizeof(two_next_str), "%d", page + 2); + context_set(&ctx, "prev_page", prev_str); + context_set(&ctx, "next_page", next_str); + context_set(&ctx, "two_prev_page", two_prev_str); + context_set(&ctx, "two_next_page", two_next_str); + if (!raw_query || strlen(raw_query) == 0) { send_redirect("/"); free_context(&ctx); @@ -810,43 +768,10 @@ int results_handler(UrlParams *params) { char ***pager_matrix = NULL; int *pager_inner_counts = NULL; - int pager_count = 0; - int pager_start = page <= 3 ? 1 : page - 2; - int pager_end = pager_start + PAGER_WINDOW_SIZE - 1; - - if (page > 3) { - char *first_href = build_search_href(raw_query, selected_engine_id, 1); - pager_count = add_link_to_collection(first_href, "First", "pagination-btn", - &pager_matrix, &pager_inner_counts, - pager_count); - free(first_href); - } - - if (page > 1) { - char *prev_href = - build_search_href(raw_query, selected_engine_id, page - 1); - pager_count = add_link_to_collection(prev_href, "Prev", "pagination-btn", - &pager_matrix, &pager_inner_counts, - pager_count); - free(prev_href); - } - - for (int i = pager_start; i <= pager_end; i++) { - char label[16]; - snprintf(label, sizeof(label), "%d", i); - char *page_href = build_search_href(raw_query, selected_engine_id, i); - pager_count = add_link_to_collection( - page_href, label, - i == page ? "pagination-btn pagination-current" : "pagination-btn", - &pager_matrix, &pager_inner_counts, pager_count); - free(page_href); - } - - char *next_href = build_search_href(raw_query, selected_engine_id, page + 1); - pager_count = add_link_to_collection(next_href, "Next", "pagination-btn", - &pager_matrix, &pager_inner_counts, - pager_count); - free(next_href); + SearchHrefData href_data = { .query = raw_query, .engine_id = selected_engine_id }; + int pager_count = build_pagination(page, locale, search_href_builder, + &href_data, &pager_matrix, + &pager_inner_counts); if (pager_count > 0) { context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix, diff --git a/src/Utility/Utility.c b/src/Utility/Utility.c index 0d7a28e..bffda4d 100644 --- a/src/Utility/Utility.c +++ b/src/Utility/Utility.c @@ -1,5 +1,6 @@ #include "Utility.h" #include +#include #include #include @@ -32,3 +33,101 @@ char *get_locale(const char *default_locale) { free(cookie); return strdup(default_locale); } + +int add_link_to_collection(const char *href, const char *label, + const char *class_name, char ****collection, + int **inner_counts, int current_count) { + char ***old_collection = *collection; + int *old_inner_counts = *inner_counts; + char ***new_collection = + (char ***)malloc(sizeof(char **) * (current_count + 1)); + int *new_inner_counts = + (int *)malloc(sizeof(int) * (current_count + 1)); + + if (!new_collection || !new_inner_counts) { + free(new_collection); + free(new_inner_counts); + return current_count; + } + + if (*collection && current_count > 0) { + memcpy(new_collection, *collection, sizeof(char **) * current_count); + } + if (*inner_counts && current_count > 0) { + memcpy(new_inner_counts, *inner_counts, sizeof(int) * current_count); + } + + *collection = new_collection; + *inner_counts = new_inner_counts; + + (*collection)[current_count] = + (char **)malloc(sizeof(char *) * LINK_FIELD_COUNT); + if (!(*collection)[current_count]) { + *collection = old_collection; + *inner_counts = old_inner_counts; + free(new_collection); + free(new_inner_counts); + return current_count; + } + + (*collection)[current_count][0] = strdup(href ? href : ""); + (*collection)[current_count][1] = strdup(label ? label : ""); + (*collection)[current_count][2] = strdup(class_name ? class_name : ""); + + if (!(*collection)[current_count][0] || !(*collection)[current_count][1] || + !(*collection)[current_count][2]) { + free((*collection)[current_count][0]); + free((*collection)[current_count][1]); + free((*collection)[current_count][2]); + free((*collection)[current_count]); + *collection = old_collection; + *inner_counts = old_inner_counts; + free(new_collection); + free(new_inner_counts); + return current_count; + } + + (*inner_counts)[current_count] = LINK_FIELD_COUNT; + + free(old_collection); + free(old_inner_counts); + return current_count + 1; +} + +int build_pagination(int page, const char *locale, + char *(*href_builder)(int page, void *data), void *data, + char ****out_matrix, int **out_inner_counts) { + enum { PAGER_WINDOW_SIZE = 5 }; + + *out_matrix = NULL; + *out_inner_counts = NULL; + int count = 0; + + int pager_start = page <= 3 ? 1 : page - 2; + int pager_end = pager_start + PAGER_WINDOW_SIZE - 1; + + if (page > 1) { + char *href = href_builder(page - 1, data); + count = add_link_to_collection(href, "<", "pagination-btn prev", + out_matrix, out_inner_counts, count); + free(href); + } + + for (int i = pager_start; i <= pager_end; i++) { + char label[16]; + snprintf(label, sizeof(label), "%d", i); + char *href = href_builder(i, data); + count = add_link_to_collection( + href, label, + i == page ? "pagination-btn pagination-current" : "pagination-btn", + out_matrix, out_inner_counts, count); + free(href); + } + + char *href = href_builder(page + 1, data); + count = add_link_to_collection(href, ">", "pagination-btn next", + out_matrix, out_inner_counts, count); + free(href); + + return count; +} diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index 387aae0..3863bc2 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -3,8 +3,18 @@ #include +#define LINK_FIELD_COUNT 3 + int hex_to_int(char c); char *get_theme(const char *default_theme); char *get_locale(const char *default_locale); +int add_link_to_collection(const char *href, const char *label, + const char *class_name, char ****collection, + int **inner_counts, int current_count); + +int build_pagination(int page, const char *locale, + char *(*href_builder)(int page, void *data), void *data, + char ****out_matrix, int **out_inner_counts); + #endif diff --git a/static/main.css b/static/main.css index 6994ade..f60c566 100644 --- a/static/main.css +++ b/static/main.css @@ -927,3 +927,5 @@ header .logo-link:hover { flex-direction: column; } } + + diff --git a/templates/images.html b/templates/images.html index f4522aa..9fd0584 100644 --- a/templates/images.html +++ b/templates/images.html @@ -65,49 +65,16 @@ {{endfor}} - + {{if exists pagination_links}} + + {{endif}} - + \ No newline at end of file diff --git a/templates/results.html b/templates/results.html index 4ce7496..54ac195 100644 --- a/templates/results.html +++ b/templates/results.html @@ -118,8 +118,8 @@ {{info[2]|safe}}

- {{l("read_more")}} - + {{l("read_more")}} + @@ -129,4 +129,4 @@ - + \ No newline at end of file From 8176078105d3ce0331cf5deb3ff2e564d5221d05 Mon Sep 17 00:00:00 2001 From: frosty Date: Wed, 1 Apr 2026 22:39:22 +0300 Subject: [PATCH 24/61] feat: configure search engines in user settings --- src/Routes/Search.c | 57 ++++++++++++++++++++++++-- src/Routes/Settings.c | 60 ++++++++++++++++++++++++++++ src/Routes/SettingsSave.c | 32 +++++++++++++++ src/Utility/Utility.c | 84 +++++++++++++++++++++++++++++++++++++++ src/Utility/Utility.h | 4 ++ 5 files changed, 233 insertions(+), 4 deletions(-) diff --git a/src/Routes/Search.c b/src/Routes/Search.c index e201e10..f0c5ad1 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -290,6 +290,13 @@ static const SearchEngine *find_enabled_engine(const char *engine_id) { return NULL; } +static int engine_allowed_for_user(const SearchEngine *eng, char **user_ids, + int user_count, int has_pref) { + if (!has_pref) + return 1; + return user_engines_contains(eng->id, user_ids, user_count); +} + static char *build_search_href(const char *query, const char *engine_id, int page) { const char *safe_query = query ? query : ""; @@ -350,6 +357,10 @@ int results_handler(UrlParams *params) { int page = 1; int btnI = 0; + char **user_engines = NULL; + int user_engine_count = 0; + int has_user_pref = (get_user_engines(&user_engines, &user_engine_count) == 0); + if (params) { for (int i = 0; i < params->count; i++) { if (strcmp(params->params[i].key, "q") == 0) { @@ -396,6 +407,11 @@ int results_handler(UrlParams *params) { if (!raw_query || strlen(raw_query) == 0) { send_redirect("/"); + if (has_user_pref) { + for (int i = 0; i < user_engine_count; i++) + free(user_engines[i]); + free(user_engines); + } free_context(&ctx); return -1; } @@ -412,7 +428,9 @@ int results_handler(UrlParams *params) { int enabled_engine_count = 0; for (int i = 0; i < ENGINE_COUNT; i++) { if (ENGINE_REGISTRY[i].enabled && - (!selected_engine || &ENGINE_REGISTRY[i] == selected_engine)) { + (!selected_engine || &ENGINE_REGISTRY[i] == selected_engine) && + engine_allowed_for_user(&ENGINE_REGISTRY[i], user_engines, + user_engine_count, has_user_pref)) { enabled_engine_count++; } } @@ -439,7 +457,9 @@ int results_handler(UrlParams *params) { int engine_idx = 0; for (int i = 0; i < ENGINE_COUNT; i++) { if (ENGINE_REGISTRY[i].enabled && - (!selected_engine || &ENGINE_REGISTRY[i] == selected_engine)) { + (!selected_engine || &ENGINE_REGISTRY[i] == selected_engine) && + engine_allowed_for_user(&ENGINE_REGISTRY[i], user_engines, + user_engine_count, has_user_pref)) { all_results[engine_idx] = NULL; jobs[engine_idx].engine = &ENGINE_REGISTRY[i]; jobs[engine_idx].query = raw_query; @@ -488,6 +508,11 @@ int results_handler(UrlParams *params) { snprintf(response, sizeof(response), "

%s

", rate_limit_msg); send_response(response); free(request_cache_key); + if (has_user_pref) { + for (int i = 0; i < user_engine_count; i++) + free(user_engines[i]); + free(user_engines); + } free_context(&ctx); return -1; } @@ -499,7 +524,9 @@ int results_handler(UrlParams *params) { int filter_engine_count = 0; for (int i = 0; i < ENGINE_COUNT; i++) { - if (ENGINE_REGISTRY[i].enabled) + if (ENGINE_REGISTRY[i].enabled && + engine_allowed_for_user(&ENGINE_REGISTRY[i], user_engines, + user_engine_count, has_user_pref)) filter_engine_count++; } @@ -516,7 +543,9 @@ int results_handler(UrlParams *params) { free(all_href); for (int i = 0; i < ENGINE_COUNT; i++) { - if (!ENGINE_REGISTRY[i].enabled) + if (!ENGINE_REGISTRY[i].enabled || + !engine_allowed_for_user(&ENGINE_REGISTRY[i], user_engines, + user_engine_count, has_user_pref)) continue; char *filter_href = @@ -575,6 +604,11 @@ int results_handler(UrlParams *params) { } } free(request_cache_key); + if (has_user_pref) { + for (int i = 0; i < user_engine_count; i++) + free(user_engines[i]); + free(user_engines); + } free_context(&ctx); if (redirect_url) { send_redirect(redirect_url); @@ -594,6 +628,11 @@ int results_handler(UrlParams *params) { } } free(request_cache_key); + if (has_user_pref) { + for (int i = 0; i < user_engine_count; i++) + free(user_engines[i]); + free(user_engines); + } free_context(&ctx); char no_results_html[128]; snprintf(no_results_html, sizeof(no_results_html), "

%s

", no_results_msg); @@ -696,6 +735,11 @@ int results_handler(UrlParams *params) { } } free(request_cache_key); + if (has_user_pref) { + for (int i = 0; i < user_engine_count; i++) + free(user_engines[i]); + free(user_engines); + } free_context(&ctx); return 0; } @@ -822,6 +866,11 @@ int results_handler(UrlParams *params) { } } free(locale); + if (has_user_pref) { + for (int i = 0; i < user_engine_count; i++) + free(user_engines[i]); + free(user_engines); + } free_context(&ctx); return 0; diff --git a/src/Routes/Settings.c b/src/Routes/Settings.c index cfadd09..1ad85a7 100644 --- a/src/Routes/Settings.c +++ b/src/Routes/Settings.c @@ -1,4 +1,5 @@ #include "Settings.h" +#include "../Scraping/Scraping.h" #include "../Utility/Utility.h" #include #include @@ -29,6 +30,48 @@ int settings_handler(UrlParams *params) { inner_counts[i] = 2; } + char **user_engines = NULL; + int user_engine_count = 0; + int has_user_pref = (get_user_engines(&user_engines, &user_engine_count) == 0); + + int enabled_count = 0; + for (int i = 0; i < ENGINE_COUNT; i++) { + if (ENGINE_REGISTRY[i].enabled) + enabled_count++; + } + + char ***engine_data = NULL; + int *engine_inner = NULL; + + if (enabled_count > 0) { + engine_data = malloc(sizeof(char **) * enabled_count); + engine_inner = malloc(sizeof(int) * enabled_count); + + int idx = 0; + for (int i = 0; i < ENGINE_COUNT; i++) { + if (!ENGINE_REGISTRY[i].enabled) + continue; + + int is_selected = !has_user_pref; + if (has_user_pref) { + for (int j = 0; j < user_engine_count; j++) { + if (strcmp(user_engines[j], ENGINE_REGISTRY[i].id) == 0) { + is_selected = 1; + break; + } + } + } + + engine_data[idx] = malloc(sizeof(char *) * 3); + engine_data[idx][0] = (char *)ENGINE_REGISTRY[i].id; + engine_data[idx][1] = (char *)ENGINE_REGISTRY[i].name; + engine_data[idx][2] = is_selected ? "checked" : ""; + engine_inner[idx] = 3; + + idx++; + } + } + TemplateContext ctx = new_context(); beaker_set_locale(&ctx, locale); context_set(&ctx, "query", query); @@ -36,9 +79,26 @@ int settings_handler(UrlParams *params) { context_set(&ctx, "locale", locale); context_set_array_of_arrays(&ctx, "locales", locale_data, locale_count, inner_counts); + if (enabled_count > 0) { + context_set_array_of_arrays(&ctx, "enabled_engines", engine_data, + enabled_count, engine_inner); + context_set(&ctx, "has_enabled_engines", "1"); + } + for (int i = 0; i < locale_count; i++) { free(locale_data[i]); } + if (engine_data) { + for (int i = 0; i < enabled_count; i++) + free(engine_data[i]); + free(engine_data); + } + free(engine_inner); + if (has_user_pref) { + for (int i = 0; i < user_engine_count; i++) + free(user_engines[i]); + free(user_engines); + } char *rendered_html = render_template("settings.html", &ctx); send_response(rendered_html); diff --git a/src/Routes/SettingsSave.c b/src/Routes/SettingsSave.c index 323fe0d..cacff50 100644 --- a/src/Routes/SettingsSave.c +++ b/src/Routes/SettingsSave.c @@ -1,11 +1,18 @@ #include "SettingsSave.h" +#include "../Scraping/Scraping.h" +#include "../Utility/Utility.h" #include #include +#define MAX_ENGINE_IDS ENGINE_COUNT + int settings_save_handler(UrlParams *params) { const char *theme = ""; const char *locale = ""; const char *query = ""; + int engines_present = 0; + char selected_ids[ENGINE_COUNT][32]; + int selected_count = 0; if (params) { for (int i = 0; i < params->count; i++) { @@ -15,6 +22,19 @@ int settings_save_handler(UrlParams *params) { locale = params->params[i].value; } else if (strcmp(params->params[i].key, "q") == 0) { query = params->params[i].value; + } else if (strcmp(params->params[i].key, "engines_present") == 0) { + engines_present = 1; + } else if (strncmp(params->params[i].key, "engine_", 7) == 0 && + strcmp(params->params[i].value, "1") == 0) { + const char *engine_id = params->params[i].key + 7; + if (engine_id[0] != '\0' && is_engine_id_enabled(engine_id) && + selected_count < ENGINE_COUNT) { + strncpy(selected_ids[selected_count], engine_id, + sizeof(selected_ids[selected_count]) - 1); + selected_ids[selected_count][sizeof(selected_ids[selected_count]) - 1] = + '\0'; + selected_count++; + } } } } @@ -26,6 +46,18 @@ int settings_save_handler(UrlParams *params) { set_cookie("locale", locale, "Fri, 31 Dec 2038 23:59:59 GMT", "/", false, false); } + if (engines_present) { + char cookie_value[512]; + cookie_value[0] = '\0'; + for (int i = 0; i < selected_count; i++) { + if (i > 0) + strcat(cookie_value, ","); + strcat(cookie_value, selected_ids[i]); + } + set_cookie("engines", cookie_value, "Fri, 31 Dec 2038 23:59:59 GMT", "/", + false, false); + } + char redirect_url[512]; snprintf(redirect_url, sizeof(redirect_url), "/settings?q=%s", query); send_redirect(redirect_url); diff --git a/src/Utility/Utility.c b/src/Utility/Utility.c index bffda4d..16656bb 100644 --- a/src/Utility/Utility.c +++ b/src/Utility/Utility.c @@ -1,4 +1,5 @@ #include "Utility.h" +#include "../Scraping/Scraping.h" #include #include #include @@ -34,6 +35,89 @@ char *get_locale(const char *default_locale) { return strdup(default_locale); } +static int engine_id_casecmp(const char *a, const char *b) { + while (*a && *b) { + char la = *a; + char lb = *b; + if (la >= 'A' && la <= 'Z') la = la - 'A' + 'a'; + if (lb >= 'A' && lb <= 'Z') lb = lb - 'A' + 'a'; + if (la != lb) return 0; + a++; + b++; + } + return *a == *b; +} + +int is_engine_id_enabled(const char *engine_id) { + if (!engine_id) return 0; + for (int i = 0; i < ENGINE_COUNT; i++) { + if (ENGINE_REGISTRY[i].enabled && + engine_id_casecmp(ENGINE_REGISTRY[i].id, engine_id)) { + return 1; + } + } + return 0; +} + +int get_user_engines(char ***out_ids, int *out_count) { + *out_ids = NULL; + *out_count = 0; + + char *cookie = get_cookie("engines"); + if (!cookie || cookie[0] == '\0') { + free(cookie); + return -1; + } + + char **ids = NULL; + int count = 0; + + char *copy = strdup(cookie); + if (!copy) { + free(cookie); + return -1; + } + + char *saveptr; + char *token = strtok_r(copy, ",", &saveptr); + while (token) { + while (*token == ' ' || *token == '\t') + token++; + + if (token[0] != '\0' && is_engine_id_enabled(token)) { + char **new_ids = realloc(ids, sizeof(char *) * (count + 1)); + if (new_ids) { + ids = new_ids; + ids[count] = strdup(token); + count++; + } + } + + token = strtok_r(NULL, ",", &saveptr); + } + + free(copy); + free(cookie); + + if (count == 0) { + free(ids); + return -1; + } + + *out_ids = ids; + *out_count = count; + return 0; +} + +int user_engines_contains(const char *engine_id, char **ids, int count) { + if (!engine_id || !ids) return 0; + for (int i = 0; i < count; i++) { + if (engine_id_casecmp(ids[i], engine_id)) + return 1; + } + return 0; +} + int add_link_to_collection(const char *href, const char *label, const char *class_name, char ****collection, int **inner_counts, int current_count) { diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index 3863bc2..822c972 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -9,6 +9,10 @@ int hex_to_int(char c); char *get_theme(const char *default_theme); char *get_locale(const char *default_locale); +int is_engine_id_enabled(const char *engine_id); +int get_user_engines(char ***out_ids, int *out_count); +int user_engines_contains(const char *engine_id, char **ids, int count); + int add_link_to_collection(const char *href, const char *label, const char *class_name, char ****collection, int **inner_counts, int current_count); From 1382d73d53af08db06b4bd88f1f466cffa9572eb Mon Sep 17 00:00:00 2001 From: frosty Date: Wed, 1 Apr 2026 22:40:10 +0300 Subject: [PATCH 25/61] feat: configure search engines in user settings p2 --- locales/ca_ca.ini | 2 ++ locales/en_uk.ini | 2 ++ locales/en_us.ini | 2 ++ static/main.css | 7 +++++++ templates/settings.html | 13 +++++++++++++ 5 files changed, 26 insertions(+) diff --git a/locales/ca_ca.ini b/locales/ca_ca.ini index 48218b9..44bc8e7 100644 --- a/locales/ca_ca.ini +++ b/locales/ca_ca.ini @@ -19,6 +19,8 @@ theme_dark = "meow" language_label = "meow" display_language_label = "meow" language_desc = "meow" +engines_label = "meow" +engines_desc = "meow" save_settings_button = "meow" no_results = "meow" error_images = "meow" diff --git a/locales/en_uk.ini b/locales/en_uk.ini index 8088abd..423c70d 100644 --- a/locales/en_uk.ini +++ b/locales/en_uk.ini @@ -19,6 +19,8 @@ theme_dark = "Dark" language_label = "Language" display_language_label = "Display Language" language_desc = "Choose your preferred language." +engines_label = "Search Engines" +engines_desc = "Choose which search engines to use. Only engines enabled by the server are shown." save_settings_button = "Save Settings" no_results = "No results found" error_images = "Error fetching images" diff --git a/locales/en_us.ini b/locales/en_us.ini index 639a208..48c235e 100644 --- a/locales/en_us.ini +++ b/locales/en_us.ini @@ -19,6 +19,8 @@ theme_dark = "Dark" language_label = "Language" display_language_label = "Display Language" language_desc = "Choose your preferred language." +engines_label = "Search Engines" +engines_desc = "Choose which search engines to use. Only engines enabled by the server are shown." save_settings_button = "Save Settings" no_results = "No results found" error_images = "Error fetching images" diff --git a/static/main.css b/static/main.css index f60c566..360fe06 100644 --- a/static/main.css +++ b/static/main.css @@ -815,6 +815,13 @@ header .logo-link:hover { border-color:var(--accent); } +.settings-checkbox { + width:18px; + height:18px; + accent-color:var(--accent); + cursor:pointer; +} + .settings-actions { display:flex; gap:12px; diff --git a/templates/settings.html b/templates/settings.html index 9d8854b..8fdd009 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -76,6 +76,19 @@ + {{if has_enabled_engines}} +
+

{{l("engines_label")}}

+

{{l("engines_desc")}}

+ + {{for eng in enabled_engines}} +
+ + +
+ {{endfor}} +
+ {{endif}}
From 478aafcf8707b8b599561a9d79c92c8b0d310f61 Mon Sep 17 00:00:00 2001 From: frosty Date: Thu, 2 Apr 2026 01:06:31 +0300 Subject: [PATCH 26/61] fix: fixed unused 'locale' parameter on pagination --- src/Routes/Images.c | 2 +- src/Routes/Search.c | 2 +- src/Utility/Utility.c | 2 +- src/Utility/Utility.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Routes/Images.c b/src/Routes/Images.c index 9d45be7..f81d331 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -73,7 +73,7 @@ int images_handler(UrlParams *params) { char ***pager_matrix = NULL; int *pager_inner_counts = NULL; - int pager_count = build_pagination(page, locale, images_href_builder, + int pager_count = build_pagination(page, images_href_builder, (void *)raw_query, &pager_matrix, &pager_inner_counts); if (pager_count > 0) { diff --git a/src/Routes/Search.c b/src/Routes/Search.c index f0c5ad1..65a7c27 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -813,7 +813,7 @@ int results_handler(UrlParams *params) { char ***pager_matrix = NULL; int *pager_inner_counts = NULL; SearchHrefData href_data = { .query = raw_query, .engine_id = selected_engine_id }; - int pager_count = build_pagination(page, locale, search_href_builder, + int pager_count = build_pagination(page, search_href_builder, &href_data, &pager_matrix, &pager_inner_counts); diff --git a/src/Utility/Utility.c b/src/Utility/Utility.c index 16656bb..b6f5a36 100644 --- a/src/Utility/Utility.c +++ b/src/Utility/Utility.c @@ -178,7 +178,7 @@ int add_link_to_collection(const char *href, const char *label, return current_count + 1; } -int build_pagination(int page, const char *locale, +int build_pagination(int page, char *(*href_builder)(int page, void *data), void *data, char ****out_matrix, int **out_inner_counts) { enum { PAGER_WINDOW_SIZE = 5 }; diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index 822c972..2002307 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -17,7 +17,7 @@ int add_link_to_collection(const char *href, const char *label, const char *class_name, char ****collection, int **inner_counts, int current_count); -int build_pagination(int page, const char *locale, +int build_pagination(int page, char *(*href_builder)(int page, void *data), void *data, char ****out_matrix, int **out_inner_counts); From f29fa38398cf5a42e72a7854fd2ee58cdfc11cbf Mon Sep 17 00:00:00 2001 From: frosty Date: Thu, 2 Apr 2026 01:16:39 +0300 Subject: [PATCH 27/61] design: changed appearance of arrows on pagination --- src/Utility/Utility.c | 4 ++-- static/main.css | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Utility/Utility.c b/src/Utility/Utility.c index b6f5a36..e6a4549 100644 --- a/src/Utility/Utility.c +++ b/src/Utility/Utility.c @@ -192,7 +192,7 @@ int build_pagination(int page, if (page > 1) { char *href = href_builder(page - 1, data); - count = add_link_to_collection(href, "<", "pagination-btn prev", + count = add_link_to_collection(href, "←", "pagination-btn prev", out_matrix, out_inner_counts, count); free(href); } @@ -209,7 +209,7 @@ int build_pagination(int page, } char *href = href_builder(page + 1, data); - count = add_link_to_collection(href, ">", "pagination-btn next", + count = add_link_to_collection(href, "→", "pagination-btn next", out_matrix, out_inner_counts, count); free(href); diff --git a/static/main.css b/static/main.css index 360fe06..1b56721 100644 --- a/static/main.css +++ b/static/main.css @@ -538,6 +538,14 @@ header .logo-link:hover { border-color: var(--accent); } +[dir="rtl"] .pagination-btn.prev { + transform: scaleX(-1); +} + +[dir="rtl"] .pagination-btn.next { + transform: scaleX(-1); +} + @media (max-width:1200px) { .content-layout { From f66686a959e764ea19bdfc7d95bbf63850b90dcd Mon Sep 17 00:00:00 2001 From: violet Date: Wed, 1 Apr 2026 01:01:21 +0500 Subject: [PATCH 28/61] fix: startpage captcha detection --- src/Scraping/Scraping.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Scraping/Scraping.c b/src/Scraping/Scraping.c index 91200f2..b81c216 100644 --- a/src/Scraping/Scraping.c +++ b/src/Scraping/Scraping.c @@ -24,7 +24,8 @@ static int response_is_startpage_captcha(const ScrapeJob *job, return response_contains(response, "Startpage Captcha") || response_contains(response, "Startpage Captcha") || - response_contains(response, "/static-pages-assets/page-data/captcha/"); + response_contains(response, "/static-pages-assets/page-data/captcha/") || + response_contains(response, ">Startpage Blocked"); } static int response_looks_like_results_page(const ScrapeJob *job, From d2e0c7f481a7002b571d7bfaff80bbe089c2a929 Mon Sep 17 00:00:00 2001 From: frosty Date: Thu, 2 Apr 2026 06:43:14 +0300 Subject: [PATCH 29/61] fix: improved speed in ImagesProxy.c --- src/Routes/ImageProxy.c | 115 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 108 insertions(+), 7 deletions(-) diff --git a/src/Routes/ImageProxy.c b/src/Routes/ImageProxy.c index 670da68..eb9c7d5 100644 --- a/src/Routes/ImageProxy.c +++ b/src/Routes/ImageProxy.c @@ -7,12 +7,25 @@ #include #include #include +#include #include #include #include #include +#include #define MAX_IMAGE_SIZE (10 * 1024 * 1024) +#define DNS_CACHE_TTL 300 + +typedef struct DnsCacheEntry { + char hostname[256]; + char ip_str[INET_ADDRSTRLEN]; + time_t resolved_at; + struct DnsCacheEntry *next; +} DnsCacheEntry; + +static DnsCacheEntry *dns_cache = NULL; +static pthread_mutex_t dns_cache_mutex = PTHREAD_MUTEX_INITIALIZER; typedef struct { char *data; @@ -20,6 +33,57 @@ typedef struct { size_t capacity; } MemoryBuffer; +static int dns_cache_lookup(const char *hostname, char *out_ip) { + time_t now = time(NULL); + pthread_mutex_lock(&dns_cache_mutex); + for (DnsCacheEntry *e = dns_cache; e; e = e->next) { + if (strcmp(e->hostname, hostname) == 0) { + if ((now - e->resolved_at) < DNS_CACHE_TTL) { + strcpy(out_ip, e->ip_str); + pthread_mutex_unlock(&dns_cache_mutex); + return 0; + } + break; + } + } + pthread_mutex_unlock(&dns_cache_mutex); + return -1; +} + +static void dns_cache_insert(const char *hostname, const char *ip_str) { + time_t now = time(NULL); + pthread_mutex_lock(&dns_cache_mutex); + + DnsCacheEntry **cursor = &dns_cache; + while (*cursor) { + DnsCacheEntry *entry = *cursor; + if ((now - entry->resolved_at) >= DNS_CACHE_TTL) { + *cursor = entry->next; + free(entry); + continue; + } + if (strcmp(entry->hostname, hostname) == 0) { + strcpy(entry->ip_str, ip_str); + entry->resolved_at = now; + pthread_mutex_unlock(&dns_cache_mutex); + return; + } + cursor = &entry->next; + } + + DnsCacheEntry *new_entry = malloc(sizeof(DnsCacheEntry)); + if (new_entry) { + strncpy(new_entry->hostname, hostname, sizeof(new_entry->hostname) - 1); + new_entry->hostname[sizeof(new_entry->hostname) - 1] = '\0'; + strcpy(new_entry->ip_str, ip_str); + new_entry->resolved_at = now; + new_entry->next = dns_cache; + dns_cache = new_entry; + } + + pthread_mutex_unlock(&dns_cache_mutex); +} + static int is_private_ip(const char *ip_str) { struct in_addr addr; if (inet_pton(AF_INET, ip_str, &addr) != 1) { @@ -59,7 +123,11 @@ static int is_private_ip(const char *ip_str) { return 0; } -static int is_private_hostname(const char *hostname) { +static const char *is_private_hostname(const char *hostname, char *out_ip) { + if (dns_cache_lookup(hostname, out_ip) == 0) { + return out_ip; + } + struct addrinfo hints, *res, *p; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; @@ -67,7 +135,7 @@ static int is_private_hostname(const char *hostname) { int err = getaddrinfo(hostname, NULL, &hints, &res); if (err != 0) { - return 0; + return NULL; } for (p = res; p != NULL; p = p->ai_next) { @@ -78,16 +146,21 @@ static int is_private_hostname(const char *hostname) { if (is_private_ip(ip_str)) { freeaddrinfo(res); - return 1; + return NULL; } + + freeaddrinfo(res); + strcpy(out_ip, ip_str); + dns_cache_insert(hostname, ip_str); + return out_ip; } } freeaddrinfo(res); - return 0; + return NULL; } -static int is_allowed_domain(const char *url) { +static int is_allowed_domain(const char *url, char *resolved_ip) { CURLU *h = curl_url(); if (!h) { return -1; @@ -132,7 +205,7 @@ static int is_allowed_domain(const char *url) { *colon = '\0'; } - if (is_private_hostname(host)) { + if (!is_private_hostname(host, resolved_ip)) { curl_url_cleanup(h); return 0; } @@ -206,7 +279,8 @@ int image_proxy_handler(UrlParams *params) { return 0; } - int domain_check = is_allowed_domain(url); + char resolved_ip[INET_ADDRSTRLEN] = {0}; + int domain_check = is_allowed_domain(url, resolved_ip); if (domain_check == -1) { send_response("Invalid URL scheme"); return 0; @@ -283,6 +357,31 @@ int image_proxy_handler(UrlParams *params) { "Chrome/120.0.0.0 Safari/537.36"); apply_proxy_settings(curl); + struct curl_slist *resolves = NULL; + if (resolved_ip[0] != '\0') { + CURLU *u = curl_url(); + if (u) { + curl_url_set(u, CURLUPART_URL, url, 0); + char *rhost = NULL; + curl_url_get(u, CURLUPART_HOST, &rhost, 0); + if (rhost) { + char *rscheme = NULL; + curl_url_get(u, CURLUPART_SCHEME, &rscheme, 0); + int port = (rscheme && strcasecmp(rscheme, "https") == 0) ? 443 : 80; + if (rscheme) + curl_free(rscheme); + + char resolve_str[512]; + snprintf(resolve_str, sizeof(resolve_str), "%s:%d:%s", rhost, port, + resolved_ip); + resolves = curl_slist_append(NULL, resolve_str); + curl_easy_setopt(curl, CURLOPT_RESOLVE, resolves); + curl_free(rhost); + } + curl_url_cleanup(u); + } + } + CURLcode res = curl_easy_perform(curl); long response_code; @@ -296,6 +395,8 @@ int image_proxy_handler(UrlParams *params) { strncpy(content_type, content_type_ptr, sizeof(content_type) - 1); } + if (resolves) + curl_slist_free_all(resolves); curl_easy_cleanup(curl); if (res != CURLE_OK || response_code != 200) { From 2fb5f975de85d1af48b98f10e88e8d639b011984 Mon Sep 17 00:00:00 2001 From: frosty Date: Thu, 2 Apr 2026 07:23:23 +0300 Subject: [PATCH 30/61] optimise: improve duplicate URL detection --- src/Routes/Search.c | 112 +++++++++++++++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 27 deletions(-) diff --git a/src/Routes/Search.c b/src/Routes/Search.c index 65a7c27..c780f59 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -12,12 +12,90 @@ #include "../Utility/Utility.h" #include "Config.h" #include +#include #include #include #include #include #include +#define URL_HASH_TABLE_SIZE 64 + +typedef struct UrlHashEntry { + char *url; + struct UrlHashEntry *next; +} UrlHashEntry; + +typedef struct { + UrlHashEntry *buckets[URL_HASH_TABLE_SIZE]; +} UrlHashTable; + +static void url_hash_init(UrlHashTable *ht) { + for (int i = 0; i < URL_HASH_TABLE_SIZE; i++) { + ht->buckets[i] = NULL; + } +} + +static unsigned int url_hash(const char *url) { + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + if (!ctx) + return 0; + EVP_DigestInit_ex(ctx, EVP_md5(), NULL); + EVP_DigestUpdate(ctx, url, strlen(url)); + EVP_DigestFinal_ex(ctx, hash, &hash_len); + EVP_MD_CTX_free(ctx); + unsigned int h = 0; + for (unsigned int i = 0; i < hash_len; i++) { + h = h * 31 + hash[i]; + } + return h % URL_HASH_TABLE_SIZE; +} + +static int url_hash_contains(UrlHashTable *ht, const char *url) { + unsigned int idx = url_hash(url); + for (UrlHashEntry *e = ht->buckets[idx]; e; e = e->next) { + if (strcmp(e->url, url) == 0) { + return 1; + } + } + return 0; +} + +static int url_hash_insert(UrlHashTable *ht, const char *url) { + unsigned int idx = url_hash(url); + for (UrlHashEntry *e = ht->buckets[idx]; e; e = e->next) { + if (strcmp(e->url, url) == 0) { + return 0; + } + } + UrlHashEntry *new_entry = malloc(sizeof(UrlHashEntry)); + if (!new_entry) + return -1; + new_entry->url = strdup(url); + if (!new_entry->url) { + free(new_entry); + return -1; + } + new_entry->next = ht->buckets[idx]; + ht->buckets[idx] = new_entry; + return 0; +} + +static void url_hash_free(UrlHashTable *ht) { + for (int i = 0; i < URL_HASH_TABLE_SIZE; i++) { + UrlHashEntry *e = ht->buckets[i]; + while (e) { + UrlHashEntry *next = e->next; + free(e->url); + free(e); + e = next; + } + ht->buckets[i] = NULL; + } +} + typedef struct { const char *query; InfoBox result; @@ -712,14 +790,7 @@ int results_handler(UrlParams *params) { if (total_results > 0) { char ***results_matrix = (char ***)malloc(sizeof(char **) * total_results); int *results_inner_counts = (int *)malloc(sizeof(int) * total_results); - char **seen_urls = (char **)malloc(sizeof(char *) * total_results); - if (!results_matrix || !results_inner_counts || !seen_urls) { - if (results_matrix) - free(results_matrix); - if (results_inner_counts) - free(results_inner_counts); - if (seen_urls) - free(seen_urls); + if (!results_matrix || !results_inner_counts) { char *html = render_template("results.html", &ctx); if (html) { send_response(html); @@ -744,37 +815,25 @@ int results_handler(UrlParams *params) { return 0; } int unique_count = 0; + UrlHashTable url_table; + url_hash_init(&url_table); for (int i = 0; i < enabled_engine_count; i++) { for (int j = 0; j < jobs[i].results_count; j++) { char *display_url = all_results[i][j].url; - int is_duplicate = 0; - for (int k = 0; k < unique_count; k++) { - if (strcmp(seen_urls[k], display_url) == 0) { - is_duplicate = 1; - break; - } - } - - if (is_duplicate) { + if (url_hash_contains(&url_table, display_url)) { free(all_results[i][j].url); free(all_results[i][j].title); free(all_results[i][j].snippet); continue; } - seen_urls[unique_count] = strdup(display_url); - if (!seen_urls[unique_count]) { - free(all_results[i][j].url); - free(all_results[i][j].title); - free(all_results[i][j].snippet); - continue; - } + url_hash_insert(&url_table, display_url); + results_matrix[unique_count] = (char **)malloc(sizeof(char *) * RESULT_FIELD_COUNT); if (!results_matrix[unique_count]) { - free(seen_urls[unique_count]); free(all_results[i][j].url); free(all_results[i][j].title); free(all_results[i][j].snippet); @@ -839,11 +898,10 @@ int results_handler(UrlParams *params) { for (int j = 0; j < RESULT_FIELD_COUNT; j++) free(results_matrix[i][j]); free(results_matrix[i]); - free(seen_urls[i]); } - free(seen_urls); free(results_matrix); free(results_inner_counts); + url_hash_free(&url_table); } else { char *html = render_template("results.html", &ctx); if (html) { From 821e670ddd3ba4fef04c52fc855b89e0aeed0a19 Mon Sep 17 00:00:00 2001 From: frosty Date: Thu, 2 Apr 2026 07:28:23 +0300 Subject: [PATCH 31/61] fix: copy locale folder in install process --- Makefile | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 0181228..6e5b227 100644 --- a/Makefile +++ b/Makefile @@ -95,9 +95,10 @@ install: @echo "Example: doas/sudo make install-openrc" install-launchd: $(TARGET) - @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(INSTALL_BIN_DIR) $(LOG_DIR) + @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(INSTALL_BIN_DIR) $(LOG_DIR) @cp -rf templates/* $(DATA_DIR)/templates/ @cp -rf static/* $(DATA_DIR)/static/ + @cp -rf locales/* $(DATA_DIR)/locales/ @cp -n example-config.ini $(DATA_DIR)/config.ini || true install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch @mkdir -p $(LAUNCHD_DIR) @@ -116,9 +117,10 @@ install-launchd: $(TARGET) @echo "Start with: sudo launchctl kickstart -k system/$(LAUNCHD_LABEL)" install-systemd: $(TARGET) - @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR) + @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR) @cp -rf templates/* $(DATA_DIR)/templates/ @cp -rf static/* $(DATA_DIR)/static/ + @cp -rf locales/* $(DATA_DIR)/locales/ @cp -n example-config.ini $(DATA_DIR)/config.ini || true install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch @echo "Setting up user '$(USER)'..." @@ -134,9 +136,10 @@ install-systemd: $(TARGET) @echo "Run 'systemctl enable --now omnisearch' to start" install-openrc: $(TARGET) - @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR) + @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR) @cp -rf templates/* $(DATA_DIR)/templates/ @cp -rf static/* $(DATA_DIR)/static/ + @cp -rf locales/* $(DATA_DIR)/locales/ @cp -n example-config.ini $(DATA_DIR)/config.ini || true install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch @echo "Setting up user '$(USER)'..." @@ -152,9 +155,10 @@ install-openrc: $(TARGET) @echo "Run 'rc-update add omnisearch default' to enable" install-runit: $(TARGET) - @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR) $(RUNIT_DIR)/omnisearch/log/ + @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR) $(RUNIT_DIR)/omnisearch/log/ @cp -rf templates/* $(DATA_DIR)/templates/ @cp -rf static/* $(DATA_DIR)/static/ + @cp -rf locales/* $(DATA_DIR)/locales/ @cp -n example-config.ini $(DATA_DIR)/config.ini || true install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch @echo "Setting up user '$(USER)'..." @@ -173,9 +177,10 @@ install-runit: $(TARGET) @echo "Artix: ln -s $(RUNIT_DIR)/omnisearch/ /run/runit/" install-s6: $(TARGET) - @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR) + @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR) @cp -rf templates/* $(DATA_DIR)/templates/ @cp -rf static/* $(DATA_DIR)/static/ + @cp -rf locales/* $(DATA_DIR)/locales/ @cp -n example-config.ini $(DATA_DIR)/config.ini || true install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch @echo "Setting up user '$(USER)'..." @@ -194,9 +199,10 @@ install-s6: $(TARGET) @echo "Service will start automatically" install-dinit: $(TARGET) - @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(LOG_DIR) $(CACHE_DIR) + @mkdir -p $(DATA_DIR)/templates $(DATA_DIR)/static $(DATA_DIR)/locales $(LOG_DIR) $(CACHE_DIR) @cp -rf templates/* $(DATA_DIR)/templates/ @cp -rf static/* $(DATA_DIR)/static/ + @cp -rf locales/* $(DATA_DIR)/locales/ @cp -n example-config.ini $(DATA_DIR)/config.ini || true install -m 755 $(TARGET) $(INSTALL_BIN_DIR)/omnisearch @echo "Setting up user '$(USER)'..." From b9f775fc2d586aafbd27dbb013abaf0020d40d7e Mon Sep 17 00:00:00 2001 From: frosty Date: Thu, 2 Apr 2026 07:39:11 +0300 Subject: [PATCH 32/61] fix: prevent SIGSEGV on NULL extract in infobox handler --- src/Routes/Search.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Routes/Search.c b/src/Routes/Search.c index c780f59..bc3492a 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -239,7 +239,7 @@ static void *infobox_thread_func(void *arg) { data->result = h->fetch_fn((char *)data->query); data->success = (data->result.title != NULL && data->result.extract != NULL && - strlen(data->result.extract) > 10); + data->result.extract[0] != '\0'); return NULL; } From 08c1aa8abeec3e31138bfba6f269738a21e83783 Mon Sep 17 00:00:00 2001 From: frosty Date: Thu, 2 Apr 2026 09:55:12 +0300 Subject: [PATCH 33/61] fix: prioritise theme files --- templates/home.html | 4 ++-- templates/images.html | 4 ++-- templates/results.html | 4 ++-- templates/settings.html | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/templates/home.html b/templates/home.html index 8bec45b..6ed3bfe 100644 --- a/templates/home.html +++ b/templates/home.html @@ -8,8 +8,8 @@ OmniSearch - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} diff --git a/templates/results.html b/templates/results.html index 54ac195..79ecd11 100644 --- a/templates/results.html +++ b/templates/results.html @@ -8,8 +8,8 @@ OmniSearch - {{query}} - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} Date: Thu, 2 Apr 2026 09:59:44 +0300 Subject: [PATCH 34/61] fix: fixed issue with last commit --- templates/home.html | 4 ++-- templates/images.html | 4 ++-- templates/results.html | 4 ++-- templates/settings.html | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/templates/home.html b/templates/home.html index 6ed3bfe..9921ac4 100644 --- a/templates/home.html +++ b/templates/home.html @@ -8,8 +8,8 @@ OmniSearch - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} diff --git a/templates/results.html b/templates/results.html index 79ecd11..2e34811 100644 --- a/templates/results.html +++ b/templates/results.html @@ -8,8 +8,8 @@ OmniSearch - {{query}} - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + {{if theme == "light"}}{{endif}} + {{if theme == "dark"}}{{endif}} Date: Fri, 3 Apr 2026 01:56:28 +0300 Subject: [PATCH 35/61] Added Latvian and Russian translations --- locales/lv_lv.ini | 32 ++++++++++++++++++++++++++++++++ locales/ru_ru.ini | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 locales/lv_lv.ini create mode 100644 locales/ru_ru.ini diff --git a/locales/lv_lv.ini b/locales/lv_lv.ini new file mode 100644 index 0000000..2a1deb2 --- /dev/null +++ b/locales/lv_lv.ini @@ -0,0 +1,32 @@ +[Meta] +Id = "lv_lv" +Name = "Latviešu" +Direction = "ltr" + +[Keys] +search_placeholder = "Meklēt tīmeklī..." +search_button = "Meklēt" +surprise_me_button = "Pārsteidz mani" +all_tab = "Viss" +images_tab = "Attēli" +settings_tab = "Iestatījumi" +settings_title = "Iestatījumi" +theme_label = "Izskats" +theme_desc = "Izvēlieties vēlamo krāsu shēmu." +theme_system = "Sistēmas" +theme_light = "Gaišs" +theme_dark = "Tumšs" +language_label = "Valoda" +display_language_label = "Saskarnes valoda" +language_desc = "Izvēlieties vēlamo valodu." +save_settings_button = "Saglabāt iestatījumus" +no_results = "Rezultāti nav atrasti" +error_images = "Kļūda, ielādējot attēlus" +rate_limit = "Lēnāk! Pārāk daudz jūsu meklēšanas vaicājumu!" +warning_fetch_error = "neizdevās izpildīt pieprasījumu, pirms OmniSearch varēja nolasīt meklēšanas rezultātus." +warning_parse_mismatch = "atgrieza meklēšanas rezultātus formātā, kuru OmniSearch nevarēja apstrādāt." +warning_blocked = "atgrieza captcha vai citu bloķēšanas lapu meklēšanas rezultātu vietā." +read_more = "Lasīt vairāk" +view_cached = "Kešēts" +view_image = "Skatīt attēlu" +visit_site = "Apmeklēt vietni" \ No newline at end of file diff --git a/locales/ru_ru.ini b/locales/ru_ru.ini new file mode 100644 index 0000000..b62fdda --- /dev/null +++ b/locales/ru_ru.ini @@ -0,0 +1,32 @@ +[Meta] +Id = "ru_ru" +Name = "Русский" +Direction = "ltr" + +[Keys] +search_placeholder = "Поиск в интернете..." +search_button = "Найти" +surprise_me_button = "Удиви меня" +all_tab = "Все" +images_tab = "Изображения" +settings_tab = "Настройки" +settings_title = "Настройки" +theme_label = "Внешний вид" +theme_desc = "Выберите предпочитаемую цветовую схему." +theme_system = "Системная" +theme_light = "Светлая" +theme_dark = "Тёмная" +language_label = "Язык" +display_language_label = "Язык интерфейса" +language_desc = "Выберите предпочитаемый язык." +save_settings_button = "Сохранить настройки" +no_results = "Ничего не найдено" +error_images = "Ошибка загрузки изображений" +rate_limit = "Не так быстро! Слишком много поисковых запросов от вас!" +warning_fetch_error = "не удалось выполнить запрос до того, как OmniSearch смог прочитать результаты поиска." +warning_parse_mismatch = "вернул результаты поиска в формате, который OmniSearch не смог обработать." +warning_blocked = "вернул капчу или другую блокирующую страницу вместо результатов поиска." +read_more = "Подробнее" +view_cached = "Сохранённая копия" +view_image = "Просмотр изображения" +visit_site = "Перейти на сайт" \ No newline at end of file From 96b57648ddca782aa93776dd9d232fd19b68b7b1 Mon Sep 17 00:00:00 2001 From: frosty Date: Thu, 2 Apr 2026 19:19:37 -0400 Subject: [PATCH 36/61] fix(locale): changed wording on en_uk, en_us to be more accurate --- locales/en_uk.ini | 2 +- locales/en_us.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en_uk.ini b/locales/en_uk.ini index 423c70d..27eb9f7 100644 --- a/locales/en_uk.ini +++ b/locales/en_uk.ini @@ -20,7 +20,7 @@ language_label = "Language" display_language_label = "Display Language" language_desc = "Choose your preferred language." engines_label = "Search Engines" -engines_desc = "Choose which search engines to use. Only engines enabled by the server are shown." +engines_desc = "Choose which search engines to use. Only engines enabled on the server are shown." save_settings_button = "Save Settings" no_results = "No results found" error_images = "Error fetching images" diff --git a/locales/en_us.ini b/locales/en_us.ini index 48c235e..2f4c27d 100644 --- a/locales/en_us.ini +++ b/locales/en_us.ini @@ -20,7 +20,7 @@ language_label = "Language" display_language_label = "Display Language" language_desc = "Choose your preferred language." engines_label = "Search Engines" -engines_desc = "Choose which search engines to use. Only engines enabled by the server are shown." +engines_desc = "Choose which search engines to use. Only engines enabled on the server are shown." save_settings_button = "Save Settings" no_results = "No results found" error_images = "Error fetching images" From 0eff62bf68b3e272853eb9781d194208dd80fed7 Mon Sep 17 00:00:00 2001 From: crumpetalpaca Date: Fri, 3 Apr 2026 11:16:57 +0100 Subject: [PATCH 37/61] fix: Use the correct contry code for English (Traditional) --- locales/{en_uk.ini => en_gb.ini} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename locales/{en_uk.ini => en_gb.ini} (98%) diff --git a/locales/en_uk.ini b/locales/en_gb.ini similarity index 98% rename from locales/en_uk.ini rename to locales/en_gb.ini index 27eb9f7..c606e85 100644 --- a/locales/en_uk.ini +++ b/locales/en_gb.ini @@ -1,5 +1,5 @@ [Meta] -Id = "en_uk" +Id = "en_gb" Name = "English (Traditional)" Direction = "ltr" From c9709029ca37ce27ea2d395b07ebb535c4bc421f Mon Sep 17 00:00:00 2001 From: frosty Date: Fri, 3 Apr 2026 11:23:38 -0400 Subject: [PATCH 38/61] fix: replaced en_uk with en_gb as default locale --- src/Routes/Home.c | 2 +- src/Routes/Images.c | 2 +- src/Routes/Search.c | 2 +- src/Routes/Settings.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Routes/Home.c b/src/Routes/Home.c index 48edfd9..bbd53b7 100644 --- a/src/Routes/Home.c +++ b/src/Routes/Home.c @@ -6,7 +6,7 @@ int home_handler(UrlParams *params) { (void)params; char *theme = get_theme(""); - char *locale = get_locale("en_uk"); + char *locale = get_locale("en_gb"); TemplateContext ctx = new_context(); context_set(&ctx, "theme", theme); diff --git a/src/Routes/Images.c b/src/Routes/Images.c index f81d331..21e6f63 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -63,7 +63,7 @@ int images_handler(UrlParams *params) { context_set(&ctx, "theme", theme); free(theme); - char *locale = get_locale("en_uk"); + char *locale = get_locale("en_gb"); beaker_set_locale(&ctx, locale); const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit"); diff --git a/src/Routes/Search.c b/src/Routes/Search.c index bc3492a..09c4b8a 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -461,7 +461,7 @@ int results_handler(UrlParams *params) { context_set(&ctx, "theme", theme); free(theme); - char *locale = get_locale("en_uk"); + char *locale = get_locale("en_gb"); beaker_set_locale(&ctx, locale); const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit"); diff --git a/src/Routes/Settings.c b/src/Routes/Settings.c index 1ad85a7..7a16595 100644 --- a/src/Routes/Settings.c +++ b/src/Routes/Settings.c @@ -16,7 +16,7 @@ int settings_handler(UrlParams *params) { } char *theme = get_theme("system"); - char *locale = get_locale("en_uk"); + char *locale = get_locale("en_gb"); LocaleInfo locales[32]; int locale_count = beaker_get_all_locales(locales, 32); From db02c4cc8028606fa57fd50424491008990ef6fd Mon Sep 17 00:00:00 2001 From: frosty Date: Fri, 3 Apr 2026 15:15:41 -0400 Subject: [PATCH 39/61] feat: added version to homepage --- Makefile | 12 ++++++++++-- src/Routes/Home.c | 1 + src/Utility/Utility.h | 4 ++++ static/main.css | 10 ++++++++++ templates/home.html | 3 +++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6e5b227..ecd6585 100644 --- a/Makefile +++ b/Makefile @@ -3,13 +3,21 @@ UNAME_S := $(shell uname -s) PKG_CONFIG ?= pkg-config PKG_DEPS := libxml-2.0 libcurl openssl +GIT_HASH := $(shell git rev-parse --short HEAD) +GIT_DATE := $(shell git log -1 --format='%ad' --date='format:%y.%m.%d') +GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) + +VERSION := $(GIT_DATE)+$(GIT_HASH)_$(GIT_BRANCH) + +CFLAGS := -Wall -Wextra -O2 -Isrc -DVERSION='"$(VERSION)"' + ifeq ($(UNAME_S),Darwin) DEP_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_DEPS) 2>/dev/null) DEP_LIBS := $(shell $(PKG_CONFIG) --libs $(PKG_DEPS) 2>/dev/null) -CFLAGS := -Wall -Wextra -O2 -Isrc $(DEP_CFLAGS) +CFLAGS += $(DEP_CFLAGS) LIBS := -lbeaker $(DEP_LIBS) -lpthread -lm else -CFLAGS := -Wall -Wextra -O2 -Isrc -I/usr/include/libxml2 +CFLAGS += -I/usr/include/libxml2 LIBS := -lbeaker -lcurl -lxml2 -lpthread -lm -lssl -lcrypto endif diff --git a/src/Routes/Home.c b/src/Routes/Home.c index bbd53b7..1e5c3df 100644 --- a/src/Routes/Home.c +++ b/src/Routes/Home.c @@ -10,6 +10,7 @@ int home_handler(UrlParams *params) { TemplateContext ctx = new_context(); context_set(&ctx, "theme", theme); + context_set(&ctx, "version", VERSION); beaker_set_locale(&ctx, locale); char *rendered_html = render_template("home.html", &ctx); send_response(rendered_html); diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index 2002307..8d13306 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -3,6 +3,10 @@ #include +#ifndef VERSION +#define VERSION "unknown" +#endif + #define LINK_FIELD_COUNT 3 int hex_to_int(char c); diff --git a/static/main.css b/static/main.css index 1b56721..2ef60c3 100644 --- a/static/main.css +++ b/static/main.css @@ -135,6 +135,16 @@ img[src=""] { mask-position:center; text-decoration:none; } +.home-footer { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding: 8px 24px; + font-size: 12px; + color: var(--text-muted); + text-align: center; +} .nav-settings-icon { width:24px; height:24px; diff --git a/templates/home.html b/templates/home.html index 9921ac4..0aef27b 100644 --- a/templates/home.html +++ b/templates/home.html @@ -39,6 +39,9 @@ +
+ {{version}} +
From 627a219bea8029107fcee77fbeea7eb79f0a79b2 Mon Sep 17 00:00:00 2001 From: frosty Date: Fri, 3 Apr 2026 15:26:01 -0400 Subject: [PATCH 40/61] feat: made version on homepage link to repo --- Makefile | 3 ++- src/Routes/Home.c | 1 + src/Utility/Utility.h | 4 ++++ static/main.css | 7 +++++++ templates/home.html | 2 +- 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ecd6585..e57c991 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,11 @@ PKG_DEPS := libxml-2.0 libcurl openssl GIT_HASH := $(shell git rev-parse --short HEAD) GIT_DATE := $(shell git log -1 --format='%ad' --date='format:%y.%m.%d') GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) +GIT_REMOTE := $(shell git remote get-url origin) VERSION := $(GIT_DATE)+$(GIT_HASH)_$(GIT_BRANCH) -CFLAGS := -Wall -Wextra -O2 -Isrc -DVERSION='"$(VERSION)"' +CFLAGS := -Wall -Wextra -O2 -Isrc -DVERSION='"$(VERSION)"' -DGIT_REMOTE='"$(GIT_REMOTE)"' ifeq ($(UNAME_S),Darwin) DEP_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(PKG_DEPS) 2>/dev/null) diff --git a/src/Routes/Home.c b/src/Routes/Home.c index 1e5c3df..be9a3d0 100644 --- a/src/Routes/Home.c +++ b/src/Routes/Home.c @@ -11,6 +11,7 @@ int home_handler(UrlParams *params) { TemplateContext ctx = new_context(); context_set(&ctx, "theme", theme); context_set(&ctx, "version", VERSION); + context_set(&ctx, "git_remote", GIT_REMOTE); beaker_set_locale(&ctx, locale); char *rendered_html = render_template("home.html", &ctx); send_response(rendered_html); diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index 8d13306..e00e50c 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -7,6 +7,10 @@ #define VERSION "unknown" #endif +#ifndef GIT_REMOTE +#define GIT_REMOTE "https://git.bwaaa.monster/omnisearch" +#endif + #define LINK_FIELD_COUNT 3 int hex_to_int(char c); diff --git a/static/main.css b/static/main.css index 2ef60c3..5811468 100644 --- a/static/main.css +++ b/static/main.css @@ -145,6 +145,13 @@ img[src=""] { color: var(--text-muted); text-align: center; } +.version-link { + color: var(--text-muted); + text-decoration: underline; +} +.version-link:hover { + color: var(--text-primary); +} .nav-settings-icon { width:24px; height:24px; diff --git a/templates/home.html b/templates/home.html index 0aef27b..65280cf 100644 --- a/templates/home.html +++ b/templates/home.html @@ -40,7 +40,7 @@ From ccbb5d71de2e62e936a78baaf329cb9f25951b55 Mon Sep 17 00:00:00 2001 From: RDJX3 Date: Fri, 3 Apr 2026 23:32:20 +0300 Subject: [PATCH 41/61] updated latvian and russian translations :D --- locales/lv_lv.ini | 14 ++++++++------ locales/ru_ru.ini | 12 +++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/locales/lv_lv.ini b/locales/lv_lv.ini index 2a1deb2..9130e3f 100644 --- a/locales/lv_lv.ini +++ b/locales/lv_lv.ini @@ -13,7 +13,7 @@ settings_tab = "Iestatījumi" settings_title = "Iestatījumi" theme_label = "Izskats" theme_desc = "Izvēlieties vēlamo krāsu shēmu." -theme_system = "Sistēmas" +theme_system = "Sistēma" theme_light = "Gaišs" theme_dark = "Tumšs" language_label = "Valoda" @@ -23,10 +23,12 @@ save_settings_button = "Saglabāt iestatījumus" no_results = "Rezultāti nav atrasti" error_images = "Kļūda, ielādējot attēlus" rate_limit = "Lēnāk! Pārāk daudz jūsu meklēšanas vaicājumu!" -warning_fetch_error = "neizdevās izpildīt pieprasījumu, pirms OmniSearch varēja nolasīt meklēšanas rezultātus." -warning_parse_mismatch = "atgrieza meklēšanas rezultātus formātā, kuru OmniSearch nevarēja apstrādāt." -warning_blocked = "atgrieza captcha vai citu bloķēšanas lapu meklēšanas rezultātu vietā." +warning_fetch_error = "Neizdevās izpildīt pieprasījumu, pirms OmniSearch varēja nolasīt meklēšanas rezultātus." +warning_parse_mismatch = "Meklēšanas rezultāti tika atgriezti formātā, kuru OmniSearch nevarēja apstrādāt." +warning_blocked = "Tika atgriezta captcha vai cita bloķējoša lapa meklēšanas rezultātu vietā." read_more = "Lasīt vairāk" -view_cached = "Kešēts" +view_cached = "Saglabātā kopija" view_image = "Skatīt attēlu" -visit_site = "Apmeklēt vietni" \ No newline at end of file +visit_site = "Apmeklēt vietni" +engines_label = "Meklēšanas dzinēji" +engines_desc = "Izvēlieties, kurus meklēšanas dzinējus izmantot. Tiek rādīti tikai serverī iespējotie." \ No newline at end of file diff --git a/locales/ru_ru.ini b/locales/ru_ru.ini index b62fdda..448f70d 100644 --- a/locales/ru_ru.ini +++ b/locales/ru_ru.ini @@ -22,11 +22,13 @@ language_desc = "Выберите предпочитаемый язык." save_settings_button = "Сохранить настройки" no_results = "Ничего не найдено" error_images = "Ошибка загрузки изображений" -rate_limit = "Не так быстро! Слишком много поисковых запросов от вас!" -warning_fetch_error = "не удалось выполнить запрос до того, как OmniSearch смог прочитать результаты поиска." -warning_parse_mismatch = "вернул результаты поиска в формате, который OmniSearch не смог обработать." -warning_blocked = "вернул капчу или другую блокирующую страницу вместо результатов поиска." +rate_limit = "Слишком много поисковых запросов. Пожалуйста, попробуйте позже." +warning_fetch_error = "Не удалось выполнить запрос до того, как OmniSearch смог прочитать результаты поиска." +warning_parse_mismatch = "Результаты поиска возвращены в формате, который OmniSearch не смог обработать." +warning_blocked = "Вместо результатов поиска возвращена капча или страница блокировки." read_more = "Подробнее" view_cached = "Сохранённая копия" view_image = "Просмотр изображения" -visit_site = "Перейти на сайт" \ No newline at end of file +visit_site = "Перейти на сайт" +engines_label = "Поисковые системы" +engines_desc = "Выберите, какие поисковые системы использовать. Отображаются только включённые на сервере." \ No newline at end of file From a675d19d4f321d8b974f170c75a68301c6a786b4 Mon Sep 17 00:00:00 2001 From: frosty Date: Fri, 3 Apr 2026 16:54:22 -0400 Subject: [PATCH 42/61] test: remove testing locale --- locales/ca_ca.ini | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 locales/ca_ca.ini diff --git a/locales/ca_ca.ini b/locales/ca_ca.ini deleted file mode 100644 index 44bc8e7..0000000 --- a/locales/ca_ca.ini +++ /dev/null @@ -1,34 +0,0 @@ -[Meta] -Id = "ca_ca" -Name = "Cat (Demo)" -Direction = "rtl" - -[Keys] -search_placeholder = "meow" -search_button = "meow" -surprise_me_button = "meow" -all_tab = "meow" -images_tab = "meow" -settings_tab = "meow" -settings_title = "meow" -theme_label = "meow" -theme_desc = "meow" -theme_system = "meow" -theme_light = "meow" -theme_dark = "meow" -language_label = "meow" -display_language_label = "meow" -language_desc = "meow" -engines_label = "meow" -engines_desc = "meow" -save_settings_button = "meow" -no_results = "meow" -error_images = "meow" -rate_limit = "meow" -warning_fetch_error = "meow" -warning_parse_mismatch = "meow" -warning_blocked = "meow" -read_more = "meow" -view_cached = "meow" -view_image = "meow" -visit_site = "meow" From 8cd861fd86bf013179736c700e7685e85e51c98a Mon Sep 17 00:00:00 2001 From: frosty Date: Fri, 3 Apr 2026 16:56:50 -0400 Subject: [PATCH 43/61] locale: renamed en_gb to 'English', en_us to 'English (US)' --- locales/en_gb.ini | 2 +- locales/en_us.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en_gb.ini b/locales/en_gb.ini index c606e85..db919a3 100644 --- a/locales/en_gb.ini +++ b/locales/en_gb.ini @@ -1,6 +1,6 @@ [Meta] Id = "en_gb" -Name = "English (Traditional)" +Name = "English" Direction = "ltr" [Keys] diff --git a/locales/en_us.ini b/locales/en_us.ini index 2f4c27d..422f220 100644 --- a/locales/en_us.ini +++ b/locales/en_us.ini @@ -1,6 +1,6 @@ [Meta] Id = "en_us" -Name = "English (Simplified)" +Name = "English (US)" Direction = "ltr" [Keys] From 7c5062dd0706c0d23d79c32fa0bcd59cb9018259 Mon Sep 17 00:00:00 2001 From: cybardev <50134239+cybardev@users.noreply.github.com> Date: Sat, 4 Apr 2026 12:47:56 -0300 Subject: [PATCH 44/61] fix: add locales and config to container --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 716b3f9..2b53982 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,3 +5,6 @@ services: dockerfile: Dockerfile ports: - "5000:5000" + volumes: + - ./locales:/app/locales + - ./example-config.ini:/etc/omnisearch/config.ini From deb3c308b847558a7c7537f9b07ea2f95c75b012 Mon Sep 17 00:00:00 2001 From: cybardev <50134239+cybardev@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:08:36 -0300 Subject: [PATCH 45/61] locale: add bn_bd (Bengali) --- locales/bn_bd.ini | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 locales/bn_bd.ini diff --git a/locales/bn_bd.ini b/locales/bn_bd.ini new file mode 100644 index 0000000..9d3bcf8 --- /dev/null +++ b/locales/bn_bd.ini @@ -0,0 +1,34 @@ +[Meta] +Id = "bn_bd" +Name = "বাংলা" +Direction = "ltr" + +[Keys] +search_placeholder = "ওয়েব অনুসন্ধান করুন..." +search_button = "অনুসন্ধান" +surprise_me_button = "চমক প্রদর্শন" +all_tab = "যাবতীয়" +images_tab = "ছবি" +settings_tab = "সেটিংস" +settings_title = "সেটিংস" +theme_label = "বাহ্যরূপ" +theme_desc = "আপনার পছন্দের বর্ণবিন্যাস নির্ধারণ করুন।" +theme_system = "সিস্টেম" +theme_light = "আলো" +theme_dark = "আঁধার" +language_label = "ভাষা" +display_language_label = "প্রদর্শিত ভাষা" +language_desc = "আপনার পছন্দের ভাষা নির্ধারণ করুন।" +engines_label = "সার্চ ইঞ্জিন" +engines_desc = "ব্যবহৃত সার্চ ইঞ্জিন নির্ধারণ করুন। শুধু সার্ভারে চলনকৃত সার্চ ইঞ্জিন দেখানো।" +save_settings_button = "সেটিংস সংরক্ষণ করুন" +no_results = "কোনো ফলাফল পাওয়া যায়নি" +error_images = "ছবি অনুসন্ধান করায় অক্ষম" +rate_limit = "আস্তে-ধীরে! বহুত অনুসন্ধান করছেন দেখি!" +warning_fetch_error = "অমনিসার্চ ফলাফল বোঝার আগেই আবেদন ব্যার্থ হয়েছে।" +warning_parse_mismatch = "অমনিসার্চ বুঝতে অক্ষম এমনপ্রকার ফলাফল ফিরেছে।" +warning_blocked = "অনুসন্ধানের ফলাফলের বদলে ক্যাপ্চা অথবা অন্য ব্লককারী পেজ ফিরেছে।" +read_more = "পড়তে থাকুন" +view_cached = "ক্যাশকৃত" +view_image = "ছবি" +visit_site = "সাইট" From e0c209c974b130a5e893c7d7b3d342f4ec325c8d Mon Sep 17 00:00:00 2001 From: cybardev <50134239+cybardev@users.noreply.github.com> Date: Sun, 5 Apr 2026 02:30:49 -0300 Subject: [PATCH 46/61] fix: match container port binding to example config --- README.md | 2 +- docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a05c227..00f2173 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ $ cd omnisearch $ docker compose up -d --build ``` -By default it can be reached on port 5000. +By default it can be reached on port 8087. ## Customisation To make your own changes while still being able to receive upstream updates: diff --git a/docker-compose.yml b/docker-compose.yml index 2b53982..249ae6a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: context: . dockerfile: Dockerfile ports: - - "5000:5000" + - "8087:8087" volumes: - ./locales:/app/locales - ./example-config.ini:/etc/omnisearch/config.ini From f6c8242e7273b50a923ae0d5c59753505fa9df9b Mon Sep 17 00:00:00 2001 From: frosty Date: Mon, 6 Apr 2026 01:56:11 -0400 Subject: [PATCH 47/61] feat: setting default locale for instance --- example-config.ini | 3 +++ src/Config.c | 3 +++ src/Config.h | 1 + src/Main.c | 4 ++++ src/Routes/Home.c | 2 +- src/Routes/Images.c | 2 +- src/Routes/Search.c | 2 +- src/Routes/Settings.c | 2 +- src/Utility/Utility.c | 12 +++++++++++- src/Utility/Utility.h | 1 + 10 files changed, 27 insertions(+), 5 deletions(-) diff --git a/example-config.ini b/example-config.ini index 5145d88..0ab4016 100644 --- a/example-config.ini +++ b/example-config.ini @@ -3,6 +3,9 @@ host = 0.0.0.0 port = 8087 domain = https://search.example.com +# Default locale (default: en_gb) +#locale = en_gb + [proxy] # Single proxy (comment out to use list_file instead) #proxy = "socks5://127.0.0.1:9050" diff --git a/src/Config.c b/src/Config.c index c4bd1f1..967a4b4 100644 --- a/src/Config.c +++ b/src/Config.c @@ -68,6 +68,9 @@ int load_config(const char *filename, Config *config) { } else if (strcmp(key, "domain") == 0) { strncpy(config->domain, value, sizeof(config->domain) - 1); config->domain[sizeof(config->domain) - 1] = '\0'; + } else if (strcmp(key, "locale") == 0) { + strncpy(config->default_locale, value, sizeof(config->default_locale) - 1); + config->default_locale[sizeof(config->default_locale) - 1] = '\0'; } } else if (strcmp(section, "proxy") == 0) { if (strcmp(key, "proxy") == 0) { diff --git a/src/Config.h b/src/Config.h index 8e68eae..25bd978 100644 --- a/src/Config.h +++ b/src/Config.h @@ -35,6 +35,7 @@ typedef struct { char host[256]; int port; char domain[256]; + char default_locale[32]; char proxy[256]; char proxy_list_file[256]; int max_proxy_retries; diff --git a/src/Main.c b/src/Main.c index 988d0b0..b6551bc 100644 --- a/src/Main.c +++ b/src/Main.c @@ -16,6 +16,7 @@ #include "Routes/Settings.h" #include "Routes/SettingsSave.h" #include "Scraping/Scraping.h" +#include "Utility/Utility.h" Config global_config; @@ -46,6 +47,7 @@ int main() { Config cfg = {.host = DEFAULT_HOST, .port = DEFAULT_PORT, .domain = "", + .default_locale = "en_gb", .proxy = "", .proxy_list_file = "", .max_proxy_retries = DEFAULT_MAX_PROXY_RETRIES, @@ -65,6 +67,8 @@ int main() { fprintf(stderr, "[WARN] Could not load config file, using defaults\n"); } + set_default_locale(cfg.default_locale); + global_config = cfg; int loaded = beaker_load_locales(); diff --git a/src/Routes/Home.c b/src/Routes/Home.c index be9a3d0..0534517 100644 --- a/src/Routes/Home.c +++ b/src/Routes/Home.c @@ -6,7 +6,7 @@ int home_handler(UrlParams *params) { (void)params; char *theme = get_theme(""); - char *locale = get_locale("en_gb"); + char *locale = get_locale(NULL); TemplateContext ctx = new_context(); context_set(&ctx, "theme", theme); diff --git a/src/Routes/Images.c b/src/Routes/Images.c index 21e6f63..dda329c 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -63,7 +63,7 @@ int images_handler(UrlParams *params) { context_set(&ctx, "theme", theme); free(theme); - char *locale = get_locale("en_gb"); + char *locale = get_locale(NULL); beaker_set_locale(&ctx, locale); const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit"); diff --git a/src/Routes/Search.c b/src/Routes/Search.c index 09c4b8a..f51fc5f 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -461,7 +461,7 @@ int results_handler(UrlParams *params) { context_set(&ctx, "theme", theme); free(theme); - char *locale = get_locale("en_gb"); + char *locale = get_locale(NULL); beaker_set_locale(&ctx, locale); const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit"); diff --git a/src/Routes/Settings.c b/src/Routes/Settings.c index 7a16595..b21dd6f 100644 --- a/src/Routes/Settings.c +++ b/src/Routes/Settings.c @@ -16,7 +16,7 @@ int settings_handler(UrlParams *params) { } char *theme = get_theme("system"); - char *locale = get_locale("en_gb"); + char *locale = get_locale(NULL); LocaleInfo locales[32]; int locale_count = beaker_get_all_locales(locales, 32); diff --git a/src/Utility/Utility.c b/src/Utility/Utility.c index e6a4549..8bcb748 100644 --- a/src/Utility/Utility.c +++ b/src/Utility/Utility.c @@ -5,6 +5,15 @@ #include #include +static char global_default_locale[32] = "en_gb"; + +void set_default_locale(const char *locale) { + if (locale && strlen(locale) > 0) { + strncpy(global_default_locale, locale, sizeof(global_default_locale) - 1); + global_default_locale[sizeof(global_default_locale) - 1] = '\0'; + } +} + int hex_to_int(char c) { if (c >= '0' && c <= '9') return c - '0'; @@ -32,7 +41,8 @@ char *get_locale(const char *default_locale) { return cookie; } free(cookie); - return strdup(default_locale); + const char *fallback = default_locale ? default_locale : global_default_locale; + return strdup(fallback); } static int engine_id_casecmp(const char *a, const char *b) { diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index e00e50c..bd48295 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -15,6 +15,7 @@ int hex_to_int(char c); char *get_theme(const char *default_theme); +void set_default_locale(const char *locale); char *get_locale(const char *default_locale); int is_engine_id_enabled(const char *engine_id); From eb2773f91ce565b94d9805a9f82aef36ed77f582 Mon Sep 17 00:00:00 2001 From: claymorwan Date: Wed, 8 Apr 2026 22:41:27 +0000 Subject: [PATCH 48/61] feat(nix): locale support --- flake.lock | 8 ++++---- flake.nix | 2 +- module.nix | 8 +++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index 468ecff..330fc96 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "beaker-src": { "flake": false, "locked": { - "lastModified": 1773884524, - "narHash": "sha256-1dnlofWaxI/YRID+WPz2jHZNDyloBubDt/bAQk9ePLU=", + "lastModified": 1775244490, + "narHash": "sha256-4TJv7X6D0l4rEbTRKf47gU43L8G5uJgxxtsqMkVixQY=", "ref": "refs/heads/master", - "rev": "abc598baf15d6f8a4de395a27ba34b1e769558e1", - "revCount": 21, + "rev": "3fab89ecf8f4c664477a82add660d28db87357b4", + "revCount": 27, "shallow": false, "type": "git", "url": "https://git.bwaaa.monster/beaker" diff --git a/flake.nix b/flake.nix index eaf5253..1a7140e 100644 --- a/flake.nix +++ b/flake.nix @@ -61,7 +61,7 @@ installPhase = '' mkdir -p $out/bin $out/share/omnisearch install -Dm755 bin/omnisearch $out/bin/omnisearch - cp -r templates static -t $out/share/omnisearch/ + cp -r templates static locales -t $out/share/omnisearch/ ''; meta = { diff --git a/module.nix b/module.nix index 40415b3..c0c7825 100644 --- a/module.nix +++ b/module.nix @@ -20,6 +20,7 @@ let host = ${cfg.settings.server.host} port = ${toString cfg.settings.server.port} domain = ${cfg.settings.server.domain} + ${lib.optionalString (cfg.settings.server.locale != null) "locale = ${cfg.settings.server.locale}"} [proxy] ${lib.optionalString (cfg.settings.proxy.proxy != null) "proxy = \"${cfg.settings.proxy.proxy}\""} @@ -64,7 +65,11 @@ in }; domain = lib.mkOption { type = lib.types.str; - default = "http://localhost:8087"; + default = "http://localhost:${toString cfg.settings.server.port}"; + }; + locale = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; }; }; proxy = { @@ -122,6 +127,7 @@ in BindReadOnlyPaths = [ "${pkg}/share/omnisearch/templates:/var/lib/omnisearch/templates" "${pkg}/share/omnisearch/static:/var/lib/omnisearch/static" + "${pkg}/share/omnisearch/locales:/var/lib/omnisearch/locales" "${finalConfigFile}:/var/lib/omnisearch/config.ini" ]; From 896b6cd266cd9353d7c3622fd8409a3988cd168a Mon Sep 17 00:00:00 2001 From: lehuy Date: Thu, 9 Apr 2026 23:14:36 +0700 Subject: [PATCH 49/61] Vietnamese language support --- locales/vi_vn.ini | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 locales/vi_vn.ini diff --git a/locales/vi_vn.ini b/locales/vi_vn.ini new file mode 100644 index 0000000..8a7a1c7 --- /dev/null +++ b/locales/vi_vn.ini @@ -0,0 +1,34 @@ +[Meta] +Id = "vi_vn" +Name = "tiếng việt" +Direction = "ltr" + +[Keys] +search_placeholder = "tìm kiếm trên web..." +search_button = "tìm kiếm" +surprise_me_button = "bất ngờ" +all_tab = "tất cả" +images_tab = "hình ảnh" +settings_tab = "cài đặt" +settings_title = "cài đặt" +theme_label = "giao diện" +theme_desc = "chọn bảng màu mà bạn muốn" +theme_system = "hệ thống" +theme_light = "sáng" +theme_dark = "tối" +language_label = "ngôn ngữ" +display_language_label = "ngôn ngữ hiển thị" +language_desc = "chọn ngôn ngữ mà bn muốn" +engines_label = "công cụ tìm kiếm" +engines_desc = "chọn các công cụ tìm kiếm để sử dụng, chỉ những công cụ dc bật trên máy chủ ms đc hiển thị." +save_settings_button = "lưu cài đặt" +no_results = "k tìm thấy kết quả" +error_images = "lỗi khi tải hình ảnh" +rate_limit = "chậm lại! dừng lại! bạn đã tìm kiếm quá nhiều lần!" +warning_fetch_error = "yêu cầu thất bại trước khi omnisearch có thể đọc kết quả tìm kiếm." +warning_parse_mismatch = "kết quả tìm kiếm được trả về ở định dạng omnisearch k thể phân tích." +warning_blocked = "trang trả về captcha hoặc trang chặn khác thay vì kết quả tìm kiếm." +read_more = "đọc thêm" +view_cached = "bản lưu" +view_image = "hình ảnh" +visit_site = "truy cập trang" From 3570cb5fb5e2ae6cfbb65cb4d62ee12c20c1d04b Mon Sep 17 00:00:00 2001 From: Else Date: Fri, 10 Apr 2026 13:27:27 +0200 Subject: [PATCH 50/61] Handle empty image proxy responses --- src/Routes/ImageProxy.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Routes/ImageProxy.c b/src/Routes/ImageProxy.c index eb9c7d5..2d6d3a9 100644 --- a/src/Routes/ImageProxy.c +++ b/src/Routes/ImageProxy.c @@ -301,6 +301,13 @@ int image_proxy_handler(UrlParams *params) { int cache_ttl = get_cache_ttl_image(); if (cache_get(cache_key, cache_ttl, &cached_data, &cached_size) == 0) { + if (!cached_data || cached_size == 0) { + free(cached_data); + free(cache_key); + send_response("Empty cached image response"); + return 0; + } + char content_type[64] = {0}; const char *ext = strrchr(url, '.'); @@ -406,6 +413,13 @@ int image_proxy_handler(UrlParams *params) { return 0; } + if (buf.size == 0) { + free(buf.data); + free(cache_key); + send_response("Empty image response"); + return 0; + } + if (strlen(content_type) == 0 || strncmp(content_type, "image/", 6) != 0) { free(buf.data); From 4368e163682e6ccb0b5b992cd31bd8feada8fd88 Mon Sep 17 00:00:00 2001 From: frosty Date: Sat, 11 Apr 2026 01:23:37 -0400 Subject: [PATCH 51/61] fix: fixed weird font issue --- static/main.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/main.css b/static/main.css index 5811468..d07ecfa 100644 --- a/static/main.css +++ b/static/main.css @@ -23,6 +23,7 @@ *, *::before, *::after { box-sizing: border-box; + font-family: sans-serif; } html { @@ -34,7 +35,6 @@ body { background-image:radial-gradient(circle at top end, var(--bg-card) 0%, var(--bg-main) 100%); background-attachment:fixed; color:var(--text-primary); - font-family:system-ui,-apple-system,sans-serif; margin:0; padding:0; min-height:100%; From 3a99a37b0c1232cf7310f5a5314466839001fec9 Mon Sep 17 00:00:00 2001 From: claymorwan Date: Mon, 13 Apr 2026 22:44:19 +0000 Subject: [PATCH 52/61] feat(locale): add french locale --- locales/fr_fr.ini | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 locales/fr_fr.ini diff --git a/locales/fr_fr.ini b/locales/fr_fr.ini new file mode 100644 index 0000000..5864778 --- /dev/null +++ b/locales/fr_fr.ini @@ -0,0 +1,34 @@ +[Meta] +Id = "fr_fr" +Name = "Français" +Direction = "ltr" + +[Keys] +search_placeholder = "Rechercher le web..." +search_button = "Rechercher" +surprise_me_button = "Surprenez-moi" +all_tab = "Tout" +images_tab = "Images" +settings_tab = "Paramètres" +settings_title = "Paramètres" +theme_label = "Apparence" +theme_desc = "Choisissez votre palette de couleurs préférée." +theme_system = "Système" +theme_light = "Clair" +theme_dark = "Sombre" +language_label = "Langue" +display_language_label = "Afficher les Langues" +language_desc = "Choisissez votre langue." +engines_label = "Moteur de Recherche" +engines_desc = "Choisissez les moteurs de recherche que vous souhaitez utiliser. Seulement les moteurs de recherche activer sur le serveur sont affichés." +save_settings_button = "Enregistrer les paramètres" +no_results = "Aucun résultats" +error_images = "Erreur lors du chargement des images" +rate_limit = "Woah ralentissez! Vous effectuer trop de Rechercher!" +warning_fetch_error = "La requête a échoué avant que OmniSearch puisse lire les résultats de la recherche." +warning_parse_mismatch = "Les résultats de la recherche ont été retourné dans un format que OmniSearch ne peux pas analyser." +warning_blocked = "Un captcha ou une autre page bloquante a été retourné au lieu d'un résultat de recherche." +read_more = "Lire Plus" +view_cached = "En cache" +view_image = "Image" +visit_site = "Site" From 6738ab0eac3363c33ff74b99dd945444943e3f72 Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 14 Apr 2026 18:13:55 -0400 Subject: [PATCH 53/61] feat: added colour code preview infobox --- src/Infobox/ColourCode.c | 166 +++++++++++++++++++++++++++++++++++++++ src/Infobox/ColourCode.h | 9 +++ src/Routes/Search.c | 6 ++ 3 files changed, 181 insertions(+) create mode 100644 src/Infobox/ColourCode.c create mode 100644 src/Infobox/ColourCode.h diff --git a/src/Infobox/ColourCode.c b/src/Infobox/ColourCode.c new file mode 100644 index 0000000..7b4d807 --- /dev/null +++ b/src/Infobox/ColourCode.c @@ -0,0 +1,166 @@ +#include "ColourCode.h" +#include +#include +#include +#include + +static const unsigned char HEX_VALS[256] = { + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, + ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['a'] = 10, ['b'] = 11, + ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15, ['A'] = 10, ['B'] = 11, + ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, +}; + +static int is_hex_digit(char c) { + return HEX_VALS[(unsigned char)c] || c == '0'; +} + +static int is_valid_hex(const char *s, int len) { + if (len != 3 && len != 6) + return 0; + for (int i = 0; i < len; i++) { + if (!is_hex_digit(s[i])) + return 0; + } + return 1; +} + +static void hex_to_rgb(const char *hex, int *r, int *g, int *b, int len) { + *r = *g = *b = 0; + if (len == 3) { + *r = HEX_VALS[(unsigned char)hex[0]] * 17; + *g = HEX_VALS[(unsigned char)hex[1]] * 17; + *b = HEX_VALS[(unsigned char)hex[2]] * 17; + } else if (len == 6) { + *r = HEX_VALS[(unsigned char)hex[0]] * 16 + HEX_VALS[(unsigned char)hex[1]]; + *g = HEX_VALS[(unsigned char)hex[2]] * 16 + HEX_VALS[(unsigned char)hex[3]]; + *b = HEX_VALS[(unsigned char)hex[4]] * 16 + HEX_VALS[(unsigned char)hex[5]]; + } +} + +static void rgb_to_hsl(int r, int g, int b, int *h, int *s, int *l) { + int max = r > g ? (r > b ? r : b) : (g > b ? g : b); + int min = r < g ? (r < b ? r : b) : (g < b ? g : b); + int delta = max - min; + *l = (max + min) / 2; + *s = 0; + *h = 0; + if (delta > 0) { + *s = (int)((double)delta / (1.0 - fabs(2.0 * *l / 255.0 - 1.0)) * 100.0); + if (max == r) { + *h = (int)(60.0 * fmod((double)(g - b) / delta, 6.0)); + } else if (max == g) { + *h = (int)(60.0 * (((b - r) / delta) + 2.0)); + } else { + *h = (int)(60.0 * (((r - g) / delta) + 4.0)); + } + if (*h < 0) + *h += 360; + } +} + +static void rgb_to_hsv(int r, int g, int b, int *h, int *s, int *v) { + int max = r > g ? (r > b ? r : b) : (g > b ? g : b); + int min = r < g ? (r < b ? r : b) : (g < b ? g : b); + int delta = max - min; + *v = max * 100 / 255; + *s = max > 0 ? delta * 100 / max : 0; + *h = 0; + if (delta > 0) { + if (max == r) { + *h = (int)(60.0 * fmod((double)(g - b) / delta, 6.0)); + } else if (max == g) { + *h = (int)(60.0 * (((b - r) / delta) + 2.0)); + } else { + *h = (int)(60.0 * (((r - g) / delta) + 4.0)); + } + if (*h < 0) + *h += 360; + } +} + +static void rgb_to_cmyk(int r, int g, int b, int *c, int *m, int *y, int *k) { + int max = r > g ? (r > b ? r : b) : (g > b ? g : b); + double kf = 1.0 - max / 255.0; + if (kf >= 0.99) { + *c = *m = *y = *k = 100; + } else { + *c = (int)((1.0 - r / 255.0 - kf) / (1.0 - kf) * 100); + *m = (int)((1.0 - g / 255.0 - kf) / (1.0 - kf) * 100); + *y = (int)((1.0 - b / 255.0 - kf) / (1.0 - kf) * 100); + *k = (int)(kf * 100); + } +} + +int is_colour_code_query(const char *query) { + if (!query) + return 0; + const char *p = query; + if (p[0] == '#') { + p++; + while (*p == ' ') + p++; + const char *end = p; + while (is_hex_digit(*end)) + end++; + int len = end - p; + return is_valid_hex(p, len); + } + return 0; +} + +InfoBox fetch_colour_data(char *query) { + InfoBox info = {NULL, NULL, NULL, NULL}; + if (!query) + return info; + + const char *p = query; + if (p[0] != '#') + return info; + p++; + while (*p == ' ') + p++; + const char *end = p; + while (is_hex_digit(*end)) + end++; + int len = end - p; + if (!is_valid_hex(p, len)) + return info; + + int r, g, b; + hex_to_rgb(p, &r, &g, &b, len); + + int h, s, l; + rgb_to_hsl(r, g, b, &h, &s, &l); + + int h2, s2, v; + rgb_to_hsv(r, g, b, &h2, &s2, &v); + + int c, m, y, k; + rgb_to_cmyk(r, g, b, &c, &m, &y, &k); + + char html[1024]; + snprintf(html, sizeof(html), + "
" + "
" + "
" + "
" + "
" + "#%.*s
" + "
RGB(%d, %d, %d)
" + "
HSL(%d, %d%%, %d%%)
" + "
HSV(%d, %d%%, %d%%)
" + "
CMYK(%d%%, %d%%, %d%%, %d%%)
" + "
" + "
" + "
", + len, p, len, p, r, g, b, h, s, l, h2, s2, v, c, m, y, k); + + info.title = strdup("Colour"); + info.extract = strdup(html); + info.thumbnail_url = NULL; + info.url = strdup("#"); + + return info; +} diff --git a/src/Infobox/ColourCode.h b/src/Infobox/ColourCode.h new file mode 100644 index 0000000..4fcfabf --- /dev/null +++ b/src/Infobox/ColourCode.h @@ -0,0 +1,9 @@ +#ifndef COLOURCODE_H +#define COLOURCODE_H + +#include "Infobox.h" + +int is_colour_code_query(const char *query); +InfoBox fetch_colour_data(char *query); + +#endif diff --git a/src/Routes/Search.c b/src/Routes/Search.c index f51fc5f..b580c10 100644 --- a/src/Routes/Search.c +++ b/src/Routes/Search.c @@ -1,6 +1,7 @@ #include "Search.h" #include "../Cache/Cache.h" #include "../Infobox/Calculator.h" +#include "../Infobox/ColourCode.h" #include "../Infobox/CurrencyConversion.h" #include "../Infobox/Dictionary.h" #include "../Infobox/UnitConversion.h" @@ -216,11 +217,16 @@ static int is_calculator_query(const char *query) { return 0; } +static InfoBox fetch_colour_wrapper(char *query) { + return fetch_colour_data(query); +} + static InfoBoxHandler handlers[] = { {is_dictionary_query, fetch_dict_wrapper, NULL}, {is_calculator_query, fetch_calc_wrapper, NULL}, {is_unit_conv_query, fetch_unit_wrapper, NULL}, {is_currency_query, fetch_currency_wrapper, NULL}, + {is_colour_code_query, fetch_colour_wrapper, NULL}, {always_true, fetch_wiki_wrapper, construct_wiki_url}, }; enum { HANDLER_COUNT = sizeof(handlers) / sizeof(handlers[0]) }; From 7665efca73708fe079e41c6fbd5d6cd42af25f7d Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 14 Apr 2026 19:27:57 -0400 Subject: [PATCH 54/61] feat: hide 'view more' button when there is no link --- templates/results.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/templates/results.html b/templates/results.html index 2e34811..f97a096 100644 --- a/templates/results.html +++ b/templates/results.html @@ -116,10 +116,12 @@ {{info[0]}} @@ -129,4 +131,4 @@ - \ No newline at end of file + From 92dd87eb57e12f9c6936e7ee2a00bf8d92b472c8 Mon Sep 17 00:00:00 2001 From: Lignum Crucis Date: Tue, 14 Apr 2026 21:37:01 -0300 Subject: [PATCH 55/61] feat(locale): add portuguese (brazil) locale --- locales/pt_br.ini | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 locales/pt_br.ini diff --git a/locales/pt_br.ini b/locales/pt_br.ini new file mode 100644 index 0000000..355dd1b --- /dev/null +++ b/locales/pt_br.ini @@ -0,0 +1,34 @@ +[Meta] +Id = "pt_br" +Name = "Portuguese (Brazil)" +Direction = "ltr" + +[Keys] +search_placeholder = "Pesquisar na internet..." +search_button = "Pesquisar" +surprise_me_button = "Me surpreenda" +all_tab = "Tudo" +images_tab = "Imagens" +settings_tab = "Configurações" +settings_title = "Configurações" +theme_label = "Aparência" +theme_desc = "Escolha o esquema de cores de sua preferência." +theme_system = "Padrão do Sistema" +theme_light = "Claro" +theme_dark = "Escuro" +language_label = "Idioma" +display_language_label = "Idioma de exibição" +language_desc = "Escolha o idioma de sua preferência" +engines_label = "Mecanismos de pesquisa" +engines_desc = "Escolha quais mecanismos de pesquisa serão utilizados. Apenas mecanismos habilitados no servidor serão mostrados." +save_settings_button = "Salvar configurações" +no_results = "Nenhum resultado encontrado" +error_images = "Erro ao buscar imagens" +rate_limit = "Calma aí! Muitas pesquisas vindo de você!" +warning_fetch_error = "pedido falhou antes que o Omnisearch pudesse ler resultados de pesquisa." +warning_parse_mismatch = "retornou resultados em um formato que o Omnisearch não pode processar." +warning_blocked = "retornou um captcha ou outra página de bloqueio ao invés de resultados de pesquisa." +read_more = "Ler mais" +view_cached = "Armazenado em cache" +view_image = "Imagem" +visit_site = "Site" From a9db276fd872951769451142137a5e0f88ed15bc Mon Sep 17 00:00:00 2001 From: m Date: Tue, 28 Apr 2026 11:44:23 +0200 Subject: [PATCH 56/61] feat(locale): add dutch (netherlands) locale --- locales/nl_nl.ini | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 locales/nl_nl.ini diff --git a/locales/nl_nl.ini b/locales/nl_nl.ini new file mode 100644 index 0000000..4ff712f --- /dev/null +++ b/locales/nl_nl.ini @@ -0,0 +1,34 @@ +[Meta] +Id = "nl_nl" +Name = "Nederlands" +Direction = "ltr" + +[Keys] +search_placeholder = "Zoek op het web..." +search_button = "Zoek" +surprise_me_button = "Verras me" +all_tab = "Alle" +images_tab = "Plaatjes" +settings_tab = "Instellingen" +settings_title = "Instellingen" +theme_label = "Kleurinstelling" +theme_desc = "Kies welke stijl je gebruikt." +theme_system = "Systeemkleur" +theme_light = "Licht" +theme_dark = "Donker" +language_label = "Taal" +display_language_label = "Geselecteerde taal" +language_desc = "Kies de taal van OmniSearch." +engines_label = "Zoekmachines" +engines_desc = "Kies welke zoekmachines je gebruikt. Alleen de ingeschakelde zoekmachines worden gebruikt." +save_settings_button = "Opslaan" +no_results = "Geen resultaten" +error_images = "Foutmelding bij het zoeken naar plaatjes." +rate_limit = "Te veel zoekopdrachten! Ga wat langzamer!" +warning_fetch_error = "stuurde een foutmelding voordat OmniSearch de resultaten kon lezen." +warning_parse_mismatch = "stuurde een foutmelding in een format dat OmniSearch niet kon lezen." +warning_blocked = "stuurde een captcha of een andere pagina, maar geen zoekresultaten." +read_more = "Lees meer" +view_cached = "Tijdelijk Opgeslagen" +view_image = "Bekijk" +visit_site = "Website" From ba6dae676a5c268d6b4265d26b9556ba1cfa6923 Mon Sep 17 00:00:00 2001 From: frosty Date: Mon, 4 May 2026 20:42:12 -0400 Subject: [PATCH 57/61] feat: remove domain field from config and derive domain from headers --- example-config.ini | 1 - src/Config.c | 6 ++---- src/Main.c | 33 +++++++++++++++++++++++++++------ templates/opensearch.xml | 2 +- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/example-config.ini b/example-config.ini index 0ab4016..2760c53 100644 --- a/example-config.ini +++ b/example-config.ini @@ -1,7 +1,6 @@ [server] host = 0.0.0.0 port = 8087 -domain = https://search.example.com # Default locale (default: en_gb) #locale = en_gb diff --git a/src/Config.c b/src/Config.c index 967a4b4..9883d45 100644 --- a/src/Config.c +++ b/src/Config.c @@ -65,11 +65,9 @@ int load_config(const char *filename, Config *config) { config->host[sizeof(config->host) - 1] = '\0'; } else if (strcmp(key, "port") == 0) { config->port = atoi(value); - } else if (strcmp(key, "domain") == 0) { - strncpy(config->domain, value, sizeof(config->domain) - 1); - config->domain[sizeof(config->domain) - 1] = '\0'; } else if (strcmp(key, "locale") == 0) { - strncpy(config->default_locale, value, sizeof(config->default_locale) - 1); + strncpy(config->default_locale, value, + sizeof(config->default_locale) - 1); config->default_locale[sizeof(config->default_locale) - 1] = '\0'; } } else if (strcmp(section, "proxy") == 0) { diff --git a/src/Main.c b/src/Main.c index b6551bc..e5cf9be 100644 --- a/src/Main.c +++ b/src/Main.c @@ -19,17 +19,38 @@ #include "Utility/Utility.h" Config global_config; - + int handle_opensearch(UrlParams *params) { (void)params; - extern Config global_config; TemplateContext ctx = new_context(); - context_set(&ctx, "domain", global_config.domain); + + const char *http_host = beaker_get_header("Host"); + if (http_host == NULL) { + http_host = "localhost"; + } + + const char *req_scheme = + "https"; // not sure if it's a good idea to just assume https, but you + // should probably be using https for anything other than testing + // or local network anyways. + + if (strncmp(http_host, "localhost", 9) == 0 || + strncmp(http_host, "127.", 4) == 0 || + strncmp(http_host, "192.168.", 8) == 0 || + strncmp(http_host, "10.", 3) == 0) { + req_scheme = "http"; + } + + context_set(&ctx, "domain", http_host); + context_set(&ctx, "scheme", req_scheme); + char *rendered = render_template("opensearch.xml", &ctx); - serve_data(rendered, strlen(rendered), "application/opensearchdescription+xml"); + serve_data(rendered, strlen(rendered), + "application/opensearchdescription+xml"); free(rendered); free_context(&ctx); + return 0; } @@ -46,7 +67,6 @@ int main() { Config cfg = {.host = DEFAULT_HOST, .port = DEFAULT_PORT, - .domain = "", .default_locale = "en_gb", .proxy = "", .proxy_list_file = "", @@ -75,7 +95,8 @@ int main() { if (loaded > 0) { fprintf(stderr, "[INFO] Loaded %d locales\n", loaded); } else { - fprintf(stderr, "[WARN] No locales loaded (make sure to run from omnisearch directory)\n"); + fprintf(stderr, "[WARN] No locales loaded (make sure to run from " + "omnisearch directory)\n"); } apply_engines_config(cfg.engines); diff --git a/templates/opensearch.xml b/templates/opensearch.xml index 8544b09..14d3760 100644 --- a/templates/opensearch.xml +++ b/templates/opensearch.xml @@ -4,7 +4,7 @@ xmlns:moz="http://www.mozilla.org/2006/browser/search/"> OmniSearch Lightweight metasearch engine - + UTF-8 UTF-8 {{domain}}/ From 3c856f93ed3d1362a8b6190d9ce44b2939fca717 Mon Sep 17 00:00:00 2001 From: frosty Date: Sun, 10 May 2026 00:00:08 -0400 Subject: [PATCH 58/61] feat(wip): load themes dynamically from static/themes/*.css --- src/Main.c | 1 + src/Routes/Home.c | 5 + src/Routes/Images.c | 17 ++- src/Routes/Settings.c | 32 ++++- src/Utility/Utility.c | 116 +++++++++++++++---- src/Utility/Utility.h | 7 +- static/main.css | 23 ---- static/{theme-dark.css => themes/dark.css} | 0 static/{theme-light.css => themes/light.css} | 0 static/themes/system.css | 22 ++++ templates/home.html | 3 +- templates/images.html | 3 +- templates/results.html | 3 +- templates/settings.html | 9 +- 14 files changed, 176 insertions(+), 65 deletions(-) rename static/{theme-dark.css => themes/dark.css} (100%) rename static/{theme-light.css => themes/light.css} (100%) create mode 100644 static/themes/system.css diff --git a/src/Main.c b/src/Main.c index e5cf9be..d7ff185 100644 --- a/src/Main.c +++ b/src/Main.c @@ -88,6 +88,7 @@ int main() { } set_default_locale(cfg.default_locale); + init_themes("static"); global_config = cfg; diff --git a/src/Routes/Home.c b/src/Routes/Home.c index 0534517..bf85fe1 100644 --- a/src/Routes/Home.c +++ b/src/Routes/Home.c @@ -2,12 +2,17 @@ #include "../Utility/Utility.h" #include #include +#include int home_handler(UrlParams *params) { (void)params; char *theme = get_theme(""); char *locale = get_locale(NULL); + char **themes = NULL; + int themes_count = 0; + get_available_themes(&themes, &themes_count); + TemplateContext ctx = new_context(); context_set(&ctx, "theme", theme); context_set(&ctx, "version", VERSION); diff --git a/src/Routes/Images.c b/src/Routes/Images.c index dda329c..40ab88f 100644 --- a/src/Routes/Images.c +++ b/src/Routes/Images.c @@ -6,6 +6,8 @@ #include "../Utility/Utility.h" #include "Config.h" #include +#include +#include static char *build_images_request_cache_key(const char *query, int page, const char *client_key) { @@ -67,15 +69,18 @@ int images_handler(UrlParams *params) { beaker_set_locale(&ctx, locale); const char *rate_limit_msg = beaker_get_locale_value(locale, "rate_limit"); - if (!rate_limit_msg) rate_limit_msg = "Slow down! Too many image searches from you!"; - const char *error_images_msg = beaker_get_locale_value(locale, "error_images"); - if (!error_images_msg) error_images_msg = "Error fetching images"; + if (!rate_limit_msg) + rate_limit_msg = "Slow down! Too many image searches from you!"; + const char *error_images_msg = + beaker_get_locale_value(locale, "error_images"); + if (!error_images_msg) + error_images_msg = "Error fetching images"; char ***pager_matrix = NULL; int *pager_inner_counts = NULL; - int pager_count = build_pagination(page, images_href_builder, - (void *)raw_query, &pager_matrix, - &pager_inner_counts); + int pager_count = + build_pagination(page, images_href_builder, (void *)raw_query, + &pager_matrix, &pager_inner_counts); if (pager_count > 0) { context_set_array_of_arrays(&ctx, "pagination_links", pager_matrix, pager_count, pager_inner_counts); diff --git a/src/Routes/Settings.c b/src/Routes/Settings.c index b21dd6f..eb3072b 100644 --- a/src/Routes/Settings.c +++ b/src/Routes/Settings.c @@ -32,7 +32,8 @@ int settings_handler(UrlParams *params) { char **user_engines = NULL; int user_engine_count = 0; - int has_user_pref = (get_user_engines(&user_engines, &user_engine_count) == 0); + int has_user_pref = + (get_user_engines(&user_engines, &user_engine_count) == 0); int enabled_count = 0; for (int i = 0; i < ENGINE_COUNT; i++) { @@ -77,7 +78,34 @@ int settings_handler(UrlParams *params) { context_set(&ctx, "query", query); context_set(&ctx, "theme", theme); context_set(&ctx, "locale", locale); - context_set_array_of_arrays(&ctx, "locales", locale_data, locale_count, inner_counts); + context_set_array_of_arrays(&ctx, "locales", locale_data, locale_count, + inner_counts); + + char **themes = NULL; + int themes_count = 0; + get_available_themes(&themes, &themes_count); + + if (themes_count > 0) { + char ***theme_ptrs = malloc(sizeof(char **) * themes_count); + int *theme_inner = malloc(sizeof(int) * themes_count); + for (int i = 0; i < themes_count; i++) { + theme_ptrs[i] = malloc(sizeof(char *) * 2); + theme_ptrs[i][0] = themes[i]; + theme_ptrs[i][1] = strdup(themes[i]); + if (theme_ptrs[i][1][0] >= 'a' && theme_ptrs[i][1][0] <= 'z') { + theme_ptrs[i][1][0] = theme_ptrs[i][1][0] - 'a' + 'A'; + } + theme_inner[i] = 2; + } + context_set_array_of_arrays(&ctx, "themes", theme_ptrs, themes_count, + theme_inner); + for (int i = 0; i < themes_count; i++) { + free(theme_ptrs[i][1]); + free(theme_ptrs[i]); + } + free(theme_ptrs); + free(theme_inner); + } if (enabled_count > 0) { context_set_array_of_arrays(&ctx, "enabled_engines", engine_data, diff --git a/src/Utility/Utility.c b/src/Utility/Utility.c index 8bcb748..1428722 100644 --- a/src/Utility/Utility.c +++ b/src/Utility/Utility.c @@ -1,11 +1,80 @@ #include "Utility.h" #include "../Scraping/Scraping.h" #include +#include #include #include #include static char global_default_locale[32] = "en_gb"; +static char **themes_list = NULL; +static int themes_count = 0; +static int themes_initialized = 0; + +void init_themes(const char *static_path) { + if (themes_initialized) + return; + themes_initialized = 1; + + char themes_dir[512]; + snprintf(themes_dir, sizeof(themes_dir), "%s/themes", static_path); + + DIR *dir = opendir(themes_dir); + if (!dir) + return; + + struct dirent *entry; + int capacity = 4; + themes_list = malloc(sizeof(char *) * capacity); + themes_count = 0; + + while ((entry = readdir(dir)) != NULL) { + size_t len = strlen(entry->d_name); + if (len > 4 && strcmp(entry->d_name + len - 4, ".css") == 0) { + if (themes_count >= capacity) { + capacity *= 2; + themes_list = realloc(themes_list, sizeof(char *) * capacity); + } + themes_list[themes_count] = strndup(entry->d_name, len - 4); + themes_count++; + } + } + closedir(dir); + + for (int i = 0; i < themes_count; i++) { + for (int j = i + 1; j < themes_count; j++) { + int priority_i = 0, priority_j = 0; + if (strcmp(themes_list[i], "system") == 0) + priority_i = 0; + else if (strcmp(themes_list[i], "light") == 0) + priority_i = 1; + else if (strcmp(themes_list[i], "dark") == 0) + priority_i = 2; + else + priority_i = 3; + if (strcmp(themes_list[j], "system") == 0) + priority_j = 0; + else if (strcmp(themes_list[j], "light") == 0) + priority_j = 1; + else if (strcmp(themes_list[j], "dark") == 0) + priority_j = 2; + else + priority_j = 3; + if (priority_i > priority_j || + (priority_i == priority_j && + strcmp(themes_list[i], themes_list[j]) > 0)) { + char *tmp = themes_list[i]; + themes_list[i] = themes_list[j]; + themes_list[j] = tmp; + } + } + } +} + +void get_available_themes(char ***out_themes, int *out_count) { + *out_themes = themes_list; + *out_count = themes_count; +} void set_default_locale(const char *locale) { if (locale && strlen(locale) > 0) { @@ -26,13 +95,16 @@ int hex_to_int(char c) { char *get_theme(const char *default_theme) { char *cookie = get_cookie("theme"); - if (cookie && - (strcmp(cookie, "light") == 0 || - strcmp(cookie, "dark") == 0)) { - return cookie; + if (cookie && strlen(cookie) > 0) { + for (int i = 0; i < themes_count; i++) { + if (strcmp(cookie, themes_list[i]) == 0) { + return cookie; + } + } } free(cookie); - return strdup(default_theme); + return strdup(default_theme && strlen(default_theme) > 0 ? default_theme + : "system"); } char *get_locale(const char *default_locale) { @@ -41,7 +113,8 @@ char *get_locale(const char *default_locale) { return cookie; } free(cookie); - const char *fallback = default_locale ? default_locale : global_default_locale; + const char *fallback = + default_locale ? default_locale : global_default_locale; return strdup(fallback); } @@ -49,9 +122,12 @@ static int engine_id_casecmp(const char *a, const char *b) { while (*a && *b) { char la = *a; char lb = *b; - if (la >= 'A' && la <= 'Z') la = la - 'A' + 'a'; - if (lb >= 'A' && lb <= 'Z') lb = lb - 'A' + 'a'; - if (la != lb) return 0; + if (la >= 'A' && la <= 'Z') + la = la - 'A' + 'a'; + if (lb >= 'A' && lb <= 'Z') + lb = lb - 'A' + 'a'; + if (la != lb) + return 0; a++; b++; } @@ -59,7 +135,8 @@ static int engine_id_casecmp(const char *a, const char *b) { } int is_engine_id_enabled(const char *engine_id) { - if (!engine_id) return 0; + if (!engine_id) + return 0; for (int i = 0; i < ENGINE_COUNT; i++) { if (ENGINE_REGISTRY[i].enabled && engine_id_casecmp(ENGINE_REGISTRY[i].id, engine_id)) { @@ -120,7 +197,8 @@ int get_user_engines(char ***out_ids, int *out_count) { } int user_engines_contains(const char *engine_id, char **ids, int count) { - if (!engine_id || !ids) return 0; + if (!engine_id || !ids) + return 0; for (int i = 0; i < count; i++) { if (engine_id_casecmp(ids[i], engine_id)) return 1; @@ -135,8 +213,7 @@ int add_link_to_collection(const char *href, const char *label, int *old_inner_counts = *inner_counts; char ***new_collection = (char ***)malloc(sizeof(char **) * (current_count + 1)); - int *new_inner_counts = - (int *)malloc(sizeof(int) * (current_count + 1)); + int *new_inner_counts = (int *)malloc(sizeof(int) * (current_count + 1)); if (!new_collection || !new_inner_counts) { free(new_collection); @@ -188,9 +265,8 @@ int add_link_to_collection(const char *href, const char *label, return current_count + 1; } -int build_pagination(int page, - char *(*href_builder)(int page, void *data), void *data, - char ****out_matrix, int **out_inner_counts) { +int build_pagination(int page, char *(*href_builder)(int page, void *data), + void *data, char ****out_matrix, int **out_inner_counts) { enum { PAGER_WINDOW_SIZE = 5 }; *out_matrix = NULL; @@ -202,8 +278,8 @@ int build_pagination(int page, if (page > 1) { char *href = href_builder(page - 1, data); - count = add_link_to_collection(href, "←", "pagination-btn prev", - out_matrix, out_inner_counts, count); + count = add_link_to_collection(href, "←", "pagination-btn prev", out_matrix, + out_inner_counts, count); free(href); } @@ -219,8 +295,8 @@ int build_pagination(int page, } char *href = href_builder(page + 1, data); - count = add_link_to_collection(href, "→", "pagination-btn next", - out_matrix, out_inner_counts, count); + count = add_link_to_collection(href, "→", "pagination-btn next", out_matrix, + out_inner_counts, count); free(href); return count; diff --git a/src/Utility/Utility.h b/src/Utility/Utility.h index bd48295..1e1de09 100644 --- a/src/Utility/Utility.h +++ b/src/Utility/Utility.h @@ -15,6 +15,8 @@ int hex_to_int(char c); char *get_theme(const char *default_theme); +void init_themes(const char *static_path); +void get_available_themes(char ***out_themes, int *out_count); void set_default_locale(const char *locale); char *get_locale(const char *default_locale); @@ -26,8 +28,7 @@ int add_link_to_collection(const char *href, const char *label, const char *class_name, char ****collection, int **inner_counts, int current_count); -int build_pagination(int page, - char *(*href_builder)(int page, void *data), void *data, - char ****out_matrix, int **out_inner_counts); +int build_pagination(int page, char *(*href_builder)(int page, void *data), + void *data, char ****out_matrix, int **out_inner_counts); #endif diff --git a/static/main.css b/static/main.css index d07ecfa..4039888 100644 --- a/static/main.css +++ b/static/main.css @@ -1,26 +1,3 @@ -:root { - --bg-main: #ffffff; - --bg-card: #f8f9fa; - --border: #e0e0e0; - --text-primary: #1a1a1a; - --text-secondary: #5f6368; - --text-muted: #757575; - --accent: #202124; - --accent-glow: rgba(0,0,0,0.05); -} -@media (prefers-color-scheme: dark) { - :root { - --bg-main: #121212; - --bg-card: #1e1e1e; - --border: #333333; - --text-primary: #ffffff; - --text-secondary: #a0a0a0; - --text-muted: #d1d1d1; - --accent: #e2e2e2; - --accent-glow: rgba(255,255,255,0.1); - } -} - *, *::before, *::after { box-sizing: border-box; font-family: sans-serif; diff --git a/static/theme-dark.css b/static/themes/dark.css similarity index 100% rename from static/theme-dark.css rename to static/themes/dark.css diff --git a/static/theme-light.css b/static/themes/light.css similarity index 100% rename from static/theme-light.css rename to static/themes/light.css diff --git a/static/themes/system.css b/static/themes/system.css new file mode 100644 index 0000000..f9c52cf --- /dev/null +++ b/static/themes/system.css @@ -0,0 +1,22 @@ +:root { + --bg-main: #ffffff; + --bg-card: #f8f9fa; + --border: #e0e0e0; + --text-primary: #1a1a1a; + --text-secondary: #5f6368; + --text-muted: #757575; + --accent: #202124; + --accent-glow: rgba(0,0,0,0.05); +} +@media (prefers-color-scheme: dark) { + :root { + --bg-main: #121212; + --bg-card: #1e1e1e; + --border: #333333; + --text-primary: #ffffff; + --text-secondary: #a0a0a0; + --text-muted: #d1d1d1; + --accent: #e2e2e2; + --accent-glow: rgba(255,255,255,0.1); + } +} \ No newline at end of file diff --git a/templates/home.html b/templates/home.html index 65280cf..9c546ca 100644 --- a/templates/home.html +++ b/templates/home.html @@ -8,8 +8,7 @@ OmniSearch - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + diff --git a/templates/results.html b/templates/results.html index f97a096..bea337f 100644 --- a/templates/results.html +++ b/templates/results.html @@ -8,8 +8,7 @@ OmniSearch - {{query}} - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + - {{if theme == "light"}}{{endif}} - {{if theme == "dark"}}{{endif}} + From 5808459c758db353d8d39df556ae42028e762321 Mon Sep 17 00:00:00 2001 From: indium114 <156162907+indium114@users.noreply.github.com> Date: Mon, 11 May 2026 08:04:23 +0200 Subject: [PATCH 59/61] feat(locale): add afrikaans (south africa) locale --- locales/af_za.ini | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 locales/af_za.ini diff --git a/locales/af_za.ini b/locales/af_za.ini new file mode 100644 index 0000000..a21ad65 --- /dev/null +++ b/locales/af_za.ini @@ -0,0 +1,34 @@ +[Meta] +Id = "af_za" +Name = "Afrikaans (ZA)" +Direction = "ltr" + +[Keys] +search_placeholder = "Soek op die web..." +search_button = "Soek" +surprise_me_button = "Verras my" +all_tab = "Alles" +images_tab = "Foto's" +settings_tab = "Instellings" +settings_title = "Instellings" +theme_label = "Kleurinstelling" +theme_desc = "Kies jou voorkeur kleurskema." +theme_system = "Stelsel" +theme_light = "Lig" +theme_dark = "Donker" +language_label = "Taal" +display_language_label = "Vertoontaal" +language_desc = "Kies jou voorkeurtaal." +engines_label = "Soekenjins" +engines_desc = "Kies watter soekenjins om te gebruik. Slegs enjins wat op die bediener geaktiveer is, word gewys.." +save_settings_button = "Stoor Instellings" +no_results = "Geen resultate gevind nie" +error_images = "Fout tydens die haal van foto's" +rate_limit = "Stadiger! Te veel soektogte van jou!" +warning_fetch_error = "versoek het misluk voordat OmniSearch soekresultate kon lees." +warning_parse_mismatch = "het soekresultate in 'n formaat teruggegee wat OmniSearch nie kon ontleed nie." +warning_blocked = "het 'n captcha of 'n ander blokkerende bladsy in plaas van soekresultate teruggegee." +read_more = "Lees Meer" +view_cached = "Gekasgeheue" +view_image = "Foto" +visit_site = "Webwerf" From c26a08c6a29416b3c59f1b2c9f65335b4409ce4f Mon Sep 17 00:00:00 2001 From: frosty Date: Tue, 2 Jun 2026 18:18:45 -0400 Subject: [PATCH 60/61] fix: some attempts to resolve some issues with images --- src/Scraping/ImageScraping.c | 178 +++++++++++++++-------------------- src/Utility/HttpClient.c | 21 ++++- 2 files changed, 95 insertions(+), 104 deletions(-) diff --git a/src/Scraping/ImageScraping.c b/src/Scraping/ImageScraping.c index 33f710a..2341244 100644 --- a/src/Scraping/ImageScraping.c +++ b/src/Scraping/ImageScraping.c @@ -28,113 +28,82 @@ static char *build_proxy_url(const char *image_url) { return proxy_url; } -static int parse_image_node(xmlNodePtr node, ImageResult *result) { - xmlNodePtr img_node = NULL; - xmlNodePtr tit_node = NULL; - xmlNodePtr des_node = NULL; - xmlNodePtr thumb_link = NULL; +static char *extract_json_string(const char *json, const char *key) { + if (!json || !key) + return NULL; - for (xmlNodePtr child = node->children; child; child = child->next) { - if (child->type != XML_ELEMENT_NODE) - continue; + char search_key[64]; + snprintf(search_key, sizeof(search_key), "\"%s\"", key); - if (xmlStrcmp(child->name, (const xmlChar *)"a") == 0) { - xmlChar *class = xmlGetProp(child, (const xmlChar *)"class"); - if (class) { - if (xmlStrstr(class, (const xmlChar *)"thumb") != NULL) { - thumb_link = child; - for (xmlNodePtr thumb_child = child->children; thumb_child; - thumb_child = thumb_child->next) { - if (xmlStrcmp(thumb_child->name, (const xmlChar *)"div") == 0) { - xmlChar *div_class = - xmlGetProp(thumb_child, (const xmlChar *)"class"); - if (div_class && - xmlStrcmp(div_class, (const xmlChar *)"cico") == 0) { - for (xmlNodePtr cico_child = thumb_child->children; cico_child; - cico_child = cico_child->next) { - if (xmlStrcmp(cico_child->name, (const xmlChar *)"img") == - 0) { - img_node = cico_child; - break; - } - } - } - if (div_class) - xmlFree(div_class); - } - } - } else if (xmlStrstr(class, (const xmlChar *)"tit") != NULL) { - tit_node = child; - } - xmlFree(class); - } - } else if (xmlStrcmp(child->name, (const xmlChar *)"div") == 0) { - xmlChar *class = xmlGetProp(child, (const xmlChar *)"class"); - if (class && xmlStrcmp(class, (const xmlChar *)"meta") == 0) { - for (xmlNodePtr meta_child = child->children; meta_child; - meta_child = meta_child->next) { - if (xmlStrcmp(meta_child->name, (const xmlChar *)"div") == 0) { - xmlChar *div_class = - xmlGetProp(meta_child, (const xmlChar *)"class"); - if (div_class) { - if (xmlStrcmp(div_class, (const xmlChar *)"des") == 0) { - des_node = meta_child; - } - xmlFree(div_class); - } - } else if (xmlStrcmp(meta_child->name, (const xmlChar *)"a") == 0) { - xmlChar *a_class = xmlGetProp(meta_child, (const xmlChar *)"class"); - if (a_class && xmlStrstr(a_class, (const xmlChar *)"tit") != NULL) { - tit_node = meta_child; - } - if (a_class) - xmlFree(a_class); - } - } - } - if (class) - xmlFree(class); - } + const char *key_pos = strstr(json, search_key); + if (!key_pos) + return NULL; + + const char *colon = strchr(key_pos + strlen(search_key), ':'); + if (!colon) + return NULL; + + colon++; + while (*colon == ' ' || *colon == '\t' || *colon == '\n' || *colon == '\r') + colon++; + + if (*colon != '"') + return NULL; + colon++; + + size_t len = 0; + const char *start = colon; + while (*colon && *colon != '"') { + if (*colon == '\\' && *(colon + 1)) + colon++; + colon++; + len++; } - xmlChar *iurl = - img_node ? xmlGetProp(img_node, (const xmlChar *)"src") : NULL; - xmlChar *full_url = - thumb_link ? xmlGetProp(thumb_link, (const xmlChar *)"href") : NULL; - xmlChar *title = des_node ? xmlNodeGetContent(des_node) - : (tit_node ? xmlNodeGetContent(tit_node) : NULL); - xmlChar *rurl = - tit_node ? xmlGetProp(tit_node, (const xmlChar *)"href") : NULL; + char *result = malloc(len + 1); + if (!result) + return NULL; - if (!iurl || strlen((char *)iurl) == 0) { - if (iurl) - xmlFree(iurl); - if (title) - xmlFree(title); - if (rurl) - xmlFree(rurl); - if (full_url) - xmlFree(full_url); + colon = start; + size_t i = 0; + while (*colon && *colon != '"') { + if (*colon == '\\' && *(colon + 1)) + colon++; + result[i++] = *colon++; + } + result[i] = '\0'; + + return result; +} + +static int parse_iusc_node(xmlNodePtr node, ImageResult *result) { + xmlChar *m_attr = xmlGetProp(node, (const xmlChar *)"m"); + if (!m_attr) return 0; + + char *turl = extract_json_string((const char *)m_attr, "turl"); + char *murl = extract_json_string((const char *)m_attr, "murl"); + char *purl = extract_json_string((const char *)m_attr, "purl"); + char *title = extract_json_string((const char *)m_attr, "t"); + + int ok = (turl != NULL && strlen(turl) > 0); + if (ok) { + char *proxy_url = build_proxy_url(turl); + result->thumbnail_url = proxy_url ? strdup(proxy_url) : strdup(turl); + free(proxy_url); + result->title = + title && strlen(title) > 0 ? strdup(title) : strdup("Image"); + result->page_url = purl && strlen(purl) > 0 ? strdup(purl) : strdup("#"); + result->full_url = murl && strlen(murl) > 0 ? strdup(murl) : strdup("#"); } - char *proxy_url = build_proxy_url((char *)iurl); - result->thumbnail_url = proxy_url ? strdup(proxy_url) : strdup((char *)iurl); - free(proxy_url); - result->title = strdup(title ? (char *)title : "Image"); - result->page_url = strdup(rurl ? (char *)rurl : "#"); - result->full_url = strdup(full_url ? (char *)full_url : "#"); + free(turl); + free(murl); + free(purl); + free(title); - if (iurl) - xmlFree(iurl); - if (title) - xmlFree(title); - if (rurl) - xmlFree(rurl); - if (full_url) - xmlFree(full_url); - - return 1; + xmlFree(m_attr); + return ok; } int scrape_images(const char *query, int page, ImageResult **out_results, @@ -157,13 +126,16 @@ int scrape_images(const char *query, int page, ImageResult **out_results, char url[BUFFER_SIZE_LARGE]; int first = (page - 1) * IMAGE_RESULTS_PER_PAGE + 1; - snprintf(url, sizeof(url), "%s?q=%s&first=%d", BING_IMAGE_URL, encoded_query, - first); + snprintf( + url, sizeof(url), + "https://www.bing.com/images/async?q=%s&async=content&first=%d&count=%d", + encoded_query, first, 35); free(encoded_query); HttpResponse resp = http_get( url, - "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"); + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, " + "like Gecko) Chrome/120.0.0.0 Safari/537.36"); if (!resp.memory) { return -1; } @@ -183,7 +155,7 @@ int scrape_images(const char *query, int page, ImageResult **out_results, } xmlXPathObjectPtr xpathObj = - xmlXPathEvalExpression((const xmlChar *)"//div[@class='item']", xpathCtx); + xmlXPathEvalExpression((const xmlChar *)"//a[@class='iusc']", xpathCtx); if (!xpathObj || !xpathObj->nodesetval) { if (xpathObj) @@ -210,7 +182,7 @@ int scrape_images(const char *query, int page, ImageResult **out_results, int count = 0; for (int i = 0; i < nodes && count < IMAGE_RESULTS_PER_PAGE; i++) { xmlNodePtr node = xpathObj->nodesetval->nodeTab[i]; - if (parse_image_node(node, &results[count])) { + if (parse_iusc_node(node, &results[count])) { count++; } } diff --git a/src/Utility/HttpClient.c b/src/Utility/HttpClient.c index bdd2f4d..0ffb9ff 100644 --- a/src/Utility/HttpClient.c +++ b/src/Utility/HttpClient.c @@ -31,6 +31,17 @@ static size_t write_callback(void *contents, size_t size, size_t nmemb, return realsize; } +static struct curl_slist *build_http_headers(void) { + struct curl_slist *headers = NULL; + headers = curl_slist_append( + headers, + "Accept: " + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + headers = curl_slist_append(headers, "Accept-Language: en-US,en;q=0.5"); + headers = curl_slist_append(headers, "DNT: 1"); + return headers; +} + HttpResponse http_get(const char *url, const char *user_agent) { HttpResponse resp = {.memory = NULL, .size = 0, .capacity = 0}; @@ -51,16 +62,24 @@ HttpResponse http_get(const char *url, const char *user_agent) { return resp; } + struct curl_slist *headers = build_http_headers(); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent ? user_agent : "libcurl-agent/1.0"); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15L); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, CURL_TIMEOUT_SECS); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, CURL_DNS_TIMEOUT_SECS); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); apply_proxy_settings(curl); CURLcode res = curl_easy_perform(curl); + curl_slist_free_all(headers); curl_easy_cleanup(curl); if (res != CURLE_OK) { From b8fd03344d1550cce1d13c753c400d00c2c11b97 Mon Sep 17 00:00:00 2001 From: indium114 <156162907+indium114@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:56:47 +0200 Subject: [PATCH 61/61] feat(themes): add catppuccin mocha theme --- static/themes/catppuccin mocha.css | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 static/themes/catppuccin mocha.css diff --git a/static/themes/catppuccin mocha.css b/static/themes/catppuccin mocha.css new file mode 100644 index 0000000..c2d5ea0 --- /dev/null +++ b/static/themes/catppuccin mocha.css @@ -0,0 +1,10 @@ +:root { + --bg-main: #181825; + --bg-card: #1e1e2e; + --border: #313244; + --text-primary: #cdd6f4; + --text-secondary: #a6adc8; + --text-muted: #6c7086; + --accent: #cba6f7; + --accent-glow: rgba(255,255,255,0.1); +}