diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..9382207 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,18 @@ +Copyright (c) 2025 SyntaxError! + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNES +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4bced34 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +# ------------------------------ +# Neocities CLI Makefile (Portable) +# ------------------------------ + +# Default compiler +CC ?= gcc + +# Compiler flags +CFLAGS ?= -Wall -O2 -Iinclude + +# Libraries +LIBS ?= -lcurl -ljansson + +# Source and target +SRC = src/main.c +BIN = neocities + +# Detect Windows (MSYS2 / MinGW) +ifeq ($(OS),Windows_NT) + RM = del /Q + BIN_EXT = .exe +else + RM = rm -f + BIN_EXT = +endif + +# Build executable +all: $(BIN)$(BIN_EXT) + +$(BIN)$(BIN_EXT): $(SRC) + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +# Clean build files +clean: + $(RM) $(BIN)$(BIN_EXT) diff --git a/README.md b/README.md index 486d96f..05d7c42 100644 --- a/README.md +++ b/README.md @@ -1 +1,121 @@ -# neocities-c \ No newline at end of file +# Neocities C CLI + +A simple command-line tool written in C that interacts with the [Neocities API](https://neocities.org/api). +Currently, only the `--info` command is implemented — it fetches and displays basic information about your Neocities site. + +--- + +## Features +- Fetch site information using Neocities API +- Authenticates via environment variables +- JSON parsing with [jansson](https://digip.org/jansson/) +- Works on Linux, Windows (MSYS2/MinGW), and Android (via Termux) +- Compilable with a Makefile for easy builds + +--- + +## Requirements + +You’ll need: +- A C compiler (`gcc` or `clang`) +- [libcurl](https://curl.se/libcurl/) +- [jansson](https://digip.org/jansson/) +- `make` + +### Linux (Debian/Ubuntu-based) +```bash +sudo apt update +sudo apt install build-essential libcurl4-openssl-dev libjansson-dev make +``` + +### Windows (MSYS2 / MinGW) +```bash +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-curl mingw-w64-x86_64-jansson make +``` + +### Android (Termux) + +```bash +pkg install clang curl-dev jansson make +``` + +## Building + +Run in cloned repository: +```bash +make +``` + +This will generate the executable neocities (or neocities.exe on Windows). + +To clean build files: + +```bash +make clean +``` + +### Environment Variables + +Before running, export your Neocities credentials: + +#### Linux / macOS / Termux + +```bash +export NEOCITIES_USER="your_username" +export NEOCITIES_PASS="your_password" +``` + +#### Windows (PowerShell) + +```batch +setx NEOCITIES_USER "your_username" +setx NEOCITIES_PASS "your_password" +``` + +## Usage + +```bash +./neocities --info +``` + +Example output: + +``` +Neocities C CLI + +Sitename: example +Hits: 42 +Created at: 2024-01-10 +Last updated: 2025-11-01 +Domain: domain.com +Tags: art, blog, personal. +``` + +## Optional: Install Globally + +You can move the compiled executable to a directory in your PATH to run it from anywhere: + +#### Linux / Termux + +```bash +sudo mv neocities /usr/local/bin/ +``` + +#### Windows (PowerShell) +Move neocities.exe to a folder in your PATH, e.g., C:\Windows\System32\. + +After that, you can run: + +```batch +neocities --info +``` + +from any folder without specifying the path. + +## Notes + +Only the --info command is implemented for now. + +If you get curl_easy_perform errors, check your network and SSL setup. + +On Termux, use clang for the best compatibility. \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..93f28c8 --- /dev/null +++ b/src/main.c @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +struct response { + char *data; + size_t len; +}; + +size_t write_callback(void *data, size_t size, size_t nmemb, void *userdata) { + struct response *resp = (struct response *)userdata; + size_t chunk_size = size * nmemb; + + char *tmp = realloc(resp->data, resp->len + chunk_size + 1); + if (!tmp) { + fprintf(stderr, "Erro de memória\n"); + return 0; + } + resp->data = tmp; + memcpy(resp->data + resp->len, data, chunk_size); + resp->len += chunk_size; + resp->data[resp->len] = '\0'; + return chunk_size; +} + +int fetch_neocities_info() { + const char *user = getenv("NEOCITIES_USER"); + const char *pass = getenv("NEOCITIES_PASS"); + if (!user || !pass) { + fprintf(stderr, "Variáveis NEOCITIES_USER ou NEOCITIES_PASS não definidas!\n"); + return 1; + } + + char auth[256]; + snprintf(auth, sizeof(auth), "%s:%s", user, pass); + + CURL *curl = curl_easy_init(); + if (!curl) { + fprintf(stderr, "Erro ao inicializar cURL\n"); + return 1; + } + + char infourl[256]; + snprintf(infourl, sizeof(infourl), "https://neocities.org/api/info?sitename=%s", user); + + curl_easy_setopt(curl, CURLOPT_URL, infourl); + curl_easy_setopt(curl, CURLOPT_USERPWD, auth); + + struct response resp = { .data = malloc(1), .len = 0 }; + resp.data[0] = '\0'; + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp); + + CURLcode res = curl_easy_perform(curl); + if (res != CURLE_OK) { + fprintf(stderr, "Erro na requisição: %s\n", curl_easy_strerror(res)); + curl_easy_cleanup(curl); + free(resp.data); + return 1; + } + curl_easy_cleanup(curl); + + json_error_t error; + json_t *obj = json_loads(resp.data, 0, &error); + free(resp.data); + + if (!obj) { + fprintf(stderr, "Erro ao parsear JSON: %s\n", error.text); + return 1; + } + + json_t *result_obj = json_object_get(obj, "result"); + const char *result_str = json_string_value(result_obj); + + if (strcmp(result_str, "error") == 0) { + const char *err_type = json_string_value(json_object_get(obj, "error_type")); + const char *err_msg = json_string_value(json_object_get(obj, "message")); + + if (strcmp(err_type, "invalid_auth") == 0) { + printf("Usuário ou senha incorretos!\n"); + } else { + printf("Erro! %s\n", err_msg); + } + json_decref(obj); + return 1; + } + + if (strcmp(result_str, "success") == 0) { + json_t *info_obj = json_object_get(obj, "info"); + printf("\nSitename: %s\n", json_string_value(json_object_get(info_obj, "sitename"))); + printf("Hits: %d\n", (int)json_integer_value(json_object_get(info_obj, "hits"))); + printf("Created at: %s\n", json_string_value(json_object_get(info_obj, "created_at"))); + printf("Last updated: %s\n", json_string_value(json_object_get(info_obj, "last_updated"))); + printf("Domain: %s\n", json_string_value(json_object_get(info_obj, "domain"))); + + json_t *tags = json_object_get(info_obj, "tags"); + printf("Tags: "); + size_t index; + json_t *tag; + json_array_foreach(tags, index, tag) { + printf("#%s%s", json_string_value(tag), (index < json_array_size(tags) - 1) ? ", " : "."); + } + printf("\n\n"); + } + + json_decref(obj); + return 0; +} + +int main(int argc, char *argv[]) { + printf("\nNeocities C CLI\n"); + + if (argc < 2 || strcmp(argv[1], "--info") != 0) { + printf("\nnothing to do.\n"); + return 0; + } + + return fetch_neocities_info(); +}