first version

This commit is contained in:
Syntax
2025-11-10 12:12:32 -03:00
parent a9820ff5a1
commit 51912b4b59
4 changed files with 296 additions and 1 deletions

18
LICENSE.txt Normal file
View File

@@ -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.

35
Makefile Normal file
View File

@@ -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)

122
README.md
View File

@@ -1 +1,121 @@
# neocities-c
# 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
Youll 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.

122
src/main.c Normal file
View File

@@ -0,0 +1,122 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include <jansson.h>
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();
}